silverstripe-framework/docs/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md

780 lines
24 KiB
Markdown
Raw Normal View History

2014-10-27 04:40:02 +01:00
title: Introduction to the Data Model and ORM
2014-10-28 04:45:46 +01:00
summary: Introduction to creating and querying a database records through the ORM (object-relational model)
2014-10-27 04:40:02 +01:00
# Introduction to the Data Model and ORM
2014-10-28 04:45:46 +01:00
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) to represent its
2014-10-27 04:40:02 +01:00
information.
2014-10-28 04:45:46 +01:00
* Each database table maps to a PHP class.
* Each database row maps to a PHP object.
* Each database column maps to a property on a PHP object.
2014-10-27 04:40:02 +01:00
All data tables in SilverStripe are defined as subclasses of [DataObject](api:SilverStripe\ORM\DataObject). The [DataObject](api:SilverStripe\ORM\DataObject) class represents a
2014-10-27 04:40:02 +01:00
single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern)
2016-01-14 11:59:53 +01:00
design pattern. Database Columns are defined as [Data Types](/developer_guides/model/data_types_and_casting) in the static `$db` variable
along with any [relationships](relations) defined as `$has_one`, `$has_many`, `$many_many` properties on the class.
2014-10-27 04:40:02 +01:00
Let's look at a simple example:
**mysite/code/Player.php**
```php
2017-08-07 05:11:17 +02:00
use SilverStripe\ORM\DataObject;
2017-08-05 00:45:24 +02:00
2017-08-07 05:11:17 +02:00
class Player extends DataObject
{
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
private static $db = [
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
'LastName' => 'Text',
'Birthday' => 'Date'
];
}
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
This `Player` class definition will create a database table `Player` with columns for `PlayerNumber`, `FirstName` and
so on. After writing this class, we need to regenerate the database schema.
## Generating the Database Schema
2014-10-28 04:45:46 +01:00
After adding, modifying or removing `DataObject` subclasses, make sure to rebuild your SilverStripe database. The
database schema is generated automatically by visiting the URL http://www.yoursite.com/dev/build while authenticated as an administrator.
2014-10-27 04:40:02 +01:00
This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema
as required.
It will perform the following changes:
* Create any missing tables
* Create any missing fields
* Create any missing indexes
* Alter the field type of any existing fields
* Rename any obsolete tables that it previously created to _obsolete_(tablename)
It **won't** do any of the following
2014-10-28 04:45:46 +01:00
* Delete tables
* Delete fields
* Rename any tables that it doesn't recognize. This allows other applications to coexist in the same database, as long as
2014-10-27 04:40:02 +01:00
their table names don't match a SilverStripe data class.
<div class="notice" markdown='1'>
You need to be logged in as an administrator to perform this command, unless your site is in [dev mode](../debugging),
or the command is run through [CLI](../cli).
</div>
When rebuilding the database schema through the [ClassLoader](api:SilverStripe\Core\Manifest\ClassLoader) the following additional properties are
2014-10-27 04:40:02 +01:00
automatically set on the `DataObject`.
* ID: Primary Key. This will use the database's built-in auto-numbering system on the base table, and apply the same ID to all subclass tables.
2014-10-27 04:40:02 +01:00
* ClassName: An enumeration listing this data-class and all of its subclasses.
* Created: A date/time field set to the creation date of this record
* LastEdited: A date/time field set to the date this record was last edited through `write()`
**mysite/code/Player.php**
```php
2017-08-07 05:11:17 +02:00
use SilverStripe\ORM\DataObject;
2017-08-05 00:45:24 +02:00
2017-08-07 05:11:17 +02:00
class Player extends DataObject
{
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
private static $db = [
'PlayerNumber' => 'Int',
'FirstName' => 'Varchar(255)',
'LastName' => 'Text',
'Birthday' => 'Date'
];
}
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
Generates the following `SQL`.
CREATE TABLE `Player` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`ClassName` enum('Player') DEFAULT 'Player',
`LastEdited` datetime DEFAULT NULL,
`Created` datetime DEFAULT NULL,
`PlayerNumber` int(11) NOT NULL DEFAULT '0',
`FirstName` varchar(255) DEFAULT NULL,
`LastName` mediumtext,
`Birthday` datetime DEFAULT NULL,
PRIMARY KEY (`ID`),
KEY `ClassName` (`ClassName`)
);
## Creating Data Records
A new instance of a [DataObject](api:SilverStripe\ORM\DataObject) can be created using the `new` syntax.
2014-10-27 04:40:02 +01:00
```php
2017-08-07 05:11:17 +02:00
$player = new Player();
```
2014-10-27 04:40:02 +01:00
Or, a better way is to use the `create` method.
```php
2017-08-07 05:11:17 +02:00
$player = Player::create();
```
2014-10-27 04:40:02 +01:00
2014-10-28 04:45:46 +01:00
<div class="notice" markdown='1'>
Using the `create()` method provides chainability, which can add elegance and brevity to your code, e.g. `Player::create()->write()`. More importantly, however, it will look up the class in the [Injector](../extending/injector) so that the class can be overriden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection).
2014-10-28 04:45:46 +01:00
</div>
2014-10-27 04:40:02 +01:00
Database columns and properties can be set as class properties on the object. The SilverStripe ORM handles the saving
of the values through a custom `__set()` method.
```php
2017-08-07 05:11:17 +02:00
$player->FirstName = "Sam";
$player->PlayerNumber = 07;
```
2014-10-27 04:40:02 +01:00
2014-10-28 04:45:46 +01:00
To save the `DataObject` to the database, use the `write()` method. The first time `write()` is called, an `ID` will be
2014-10-27 04:40:02 +01:00
set.
```php
2017-08-07 05:11:17 +02:00
$player->write();
```
2014-10-27 04:40:02 +01:00
2014-10-28 04:45:46 +01:00
For convenience, the `write()` method returns the record's ID. This is particularly useful when creating new records.
```php
2017-08-07 05:11:17 +02:00
$player = Player::create();
$id = $player->write();
```
2014-10-28 04:45:46 +01:00
2014-10-27 04:40:02 +01:00
## Querying Data
With the `Player` class defined we can query our data using the `ORM` or Object-Relational Model. The `ORM` provides
shortcuts and methods for fetching, sorting and filtering data from our database.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get();
// returns a `DataList` containing all the `Player` objects.
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
$player = Player::get()->byID(2);
// returns a single `Player` object instance that has the ID of 2.
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
echo $player->ID;
// returns the players 'ID' column value
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
echo $player->dbObject('LastEdited')->Ago();
// calls the `Ago` method on the `LastEdited` property.
```
2014-10-27 04:40:02 +01:00
The `ORM` uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods
are `filter()` and `sort()`:
```php
2017-08-07 05:11:17 +02:00
$members = Player::get()->filter([
'FirstName' => 'Sam'
])->sort('Surname');
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// returns a `DataList` containing all the `Player` records that have the `FirstName` of 'Sam'
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
<div class="info" markdown="1">
Provided `filter` values are automatically escaped and do not require any escaping.
</div>
## Lazy Loading
The `ORM` doesn't actually execute the [SQLSelect](api:SilverStripe\ORM\Queries\SQLSelect) until you iterate on the result with a `foreach()` or `<% loop %>`.
2014-10-27 04:40:02 +01:00
2014-10-28 04:45:46 +01:00
It's smart enough to generate a single efficient query at the last moment in time without needing to post-process the
2014-10-27 04:40:02 +01:00
result set in PHP. In `MySQL` the query generated by the ORM may look something like this
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter([
'FirstName' => 'Sam'
]);
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
$players = $players->sort('Surname');
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// executes the following single query
// SELECT * FROM Player WHERE FirstName = 'Sam' ORDER BY Surname
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
This also means that getting the count of a list of objects will be done with a single, efficient query.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter([
'FirstName' => 'Sam'
])->sort('Surname');
// This will create an single SELECT COUNT query
// SELECT COUNT(*) FROM Player WHERE FirstName = 'Sam'
echo $players->Count();
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
## Looping over a list of objects
`get()` returns a `DataList` instance. You can loop over `DataList` instances in both PHP and templates.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get();
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
foreach($players as $player) {
echo $player->FirstName;
}
```
2014-10-27 04:40:02 +01:00
2014-10-28 04:45:46 +01:00
Notice that we can step into the loop safely without having to check if `$players` exists. The `get()` call is robust, and will at worst return an empty `DataList` object. If you do want to check if the query returned any records, you can use the `exists()` method, e.g.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get();
2014-10-28 04:45:46 +01:00
2017-08-07 05:11:17 +02:00
if($players->exists()) {
// do something here
}
```
2014-10-28 04:45:46 +01:00
See the [Lists](lists) documentation for more information on dealing with [SS_List](api:SilverStripe\ORM\SS_List) instances.
2014-10-27 04:40:02 +01:00
## Returning a single DataObject
There are a couple of ways of getting a single DataObject from the ORM. If you know the ID number of the object, you
can use `byID($id)`:
```php
2017-08-07 05:11:17 +02:00
$player = Player::get()->byID(5);
```
2014-10-27 04:40:02 +01:00
`get()` returns a [DataList](api:SilverStripe\ORM\DataList) instance. You can use operations on that to get back a single record.
2014-10-27 04:40:02 +01:00
```php
2017-08-07 05:11:17 +02:00
$players = Player::get();
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
$first = $players->first();
$last = $players->last();
```
2014-10-27 04:40:02 +01:00
## Sorting
If would like to sort the list by `FirstName` in a ascending way (from A to Z).
```php
2017-08-07 05:11:17 +02:00
// Sort can either be Ascending (ASC) or Descending (DESC)
$players = Player::get()->sort('FirstName', 'ASC');
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// Ascending is implied
$players = Player::get()->sort('FirstName');
```
2014-10-27 04:40:02 +01:00
To reverse the sort
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->sort('FirstName', 'DESC');
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// or..
$players = Player::get()->sort('FirstName', 'ASC')->reverse();
```
2014-10-27 04:40:02 +01:00
However you might have several entries with the same `FirstName` and would like to sort them by `FirstName` and
`LastName`
```php
2017-08-07 05:11:17 +02:00
$players = Players::get()->sort([
'FirstName' => 'ASC',
'LastName'=>'ASC'
]);
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
You can also sort randomly. Using the `DB` class, you can get the random sort method per database type.
2014-10-27 04:40:02 +01:00
```php
2017-08-07 05:11:17 +02:00
$random = DB::get_conn()->random();
$players = Player::get()->sort($random)
```
2014-10-27 04:40:02 +01:00
## Filtering Results
The `filter()` method filters the list of objects that gets returned.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter([
'FirstName' => 'Sam'
]);
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must be
true for the record to be included in the result.
The key in the filter corresponds to the field that you want to filter and the value in the filter corresponds to the
value that you want to filter to.
So, this would return only those players called "Sam Minnée".
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter([
'FirstName' => 'Sam',
'LastName' => 'Minnée',
]);
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// SELECT * FROM Player WHERE FirstName = 'Sam' AND LastName = 'Minnée'
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
2014-10-28 04:45:46 +01:00
There is also a shorthand way of getting Players with the FirstName of Sam.
2014-10-27 04:40:02 +01:00
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter('FirstName', 'Sam');
```
2014-10-27 04:40:02 +01:00
Or if you want to find both Sam and Sig.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter(
'FirstName', ['Sam', 'Sig']
);
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// SELECT * FROM Player WHERE FirstName IN ('Sam', 'Sig')
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
You can use [SearchFilters](searchfilters) to add additional behavior to your `filter` command rather than an
exact match.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter([
'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10'
]);
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
### filterAny
Use the `filterAny()` method to match multiple criteria non-exclusively (with an "OR" disjunctive),
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filterAny([
'FirstName' => 'Sam',
'Age' => 17,
]);
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// SELECT * FROM Player WHERE ("FirstName" = 'Sam' OR "Age" = '17')
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
You can combine both conjunctive ("AND") and disjunctive ("OR") statements.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()
->filter([
'LastName' => 'Minnée'
])
->filterAny([
'FirstName' => 'Sam',
'Age' => 17,
]);
// SELECT * FROM Player WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
You can use [SearchFilters](searchfilters) to add additional behavior to your `filterAny` command.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filterAny([
'FirstName:StartsWith' => 'S'
'PlayerNumber:GreaterThan' => '10'
]);
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
### Filtering by null values
Since null values in SQL are special, they are non-comparable with other values, certain filters will add
`IS NULL` or `IS NOT NULL` predicates automatically to your query. As per ANSI SQL-92, any comparison
condition against a field will filter out nulls by default. Therefore, it's necessary to include certain null
checks to ensure that exclusion filters behave predictably.
For instance, the below code will select only values that do not match the given value, including nulls.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter('FirstName:not', 'Sam');
// ... WHERE "FirstName" != 'Sam' OR "FirstName" IS NULL
// Returns rows with any value (even null) other than Sam
```
If null values should be excluded, include the null in your check.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter('FirstName:not', ['Sam', null]);
// ... WHERE "FirstName" != 'Sam' AND "FirstName" IS NOT NULL
// Only returns non-null values for "FirstName" that aren't Sam.
// Strictly the IS NOT NULL isn't necessary, but is included for explicitness
2017-08-03 05:35:09 +02:00
```
It is also often useful to filter by all rows with either empty or null for a given field.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filter('FirstName', [null, '']);
// ... WHERE "FirstName" == '' OR "FirstName" IS NULL
// Returns rows with FirstName which is either empty or null
2017-08-03 05:35:09 +02:00
```
### Filtering by aggregates
You can use aggregate expressions in your filters, as well.
```php
2017-08-07 05:11:17 +02:00
// get the teams that have more than 10 players
$teams = Team::get()->filter('Players.Count():GreaterThan', 10);
2017-08-07 05:11:17 +02:00
// get the teams with at least one player who has scored 5 or more points
$teams = Team::get()->filter('Players.Min(PointsScored):GreaterThanOrEqual', 5);
2017-08-07 05:11:17 +02:00
// get the teams with players who are averaging more than 15 points
$teams = Team::get()->filter('Players.Avg(PointsScored):GreaterThan', 15);
2017-08-07 05:11:17 +02:00
// get the teams whose players have scored less than 300 points combined
$teams = Team::get()->filter('Players.Sum(PointsScored):LessThan', 300);
```
2014-10-27 04:40:02 +01:00
### filterByCallback
It is also possible to filter by a PHP callback, this will force the data model to fetch all records and loop them in
PHP, thus `filter()` or `filterAny()` are to be preferred over `filterByCallback()`.
<div class="notice" markdown="1">
2014-10-28 04:45:46 +01:00
Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets.
`filterByCallback()` will always return an `ArrayList`.
2014-10-27 04:40:02 +01:00
</div>
The first parameter to the callback is the item, the second parameter is the list itself. The callback will run once
for each record, if the callback returns true, this record will be added to the list of returned items.
The below example will get all `Players` aged over 10.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->filterByCallback(function($item, $list) {
return ($item->Age() > 10);
});
```
2014-10-27 04:40:02 +01:00
### Exclude
The `exclude()` method is the opposite to the filter in that it removes entries from a list.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->exclude('FirstName', 'Sam');
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// SELECT * FROM Player WHERE FirstName != 'Sam'
```
2014-10-27 04:40:02 +01:00
Remove both Sam and Sig..
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->exclude(
'FirstName', ['Sam','Sig']
);
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
`Exclude` follows the same pattern as filter, so for removing only Sam Minnée from the list:
```php
2014-10-27 04:40:02 +01:00
$players = Player::get()->exclude(array(
'FirstName' => 'Sam',
'Surname' => 'Minnée',
));
// SELECT * FROM Player WHERE (FirstName != 'Sam' OR LastName != 'Minnée')
2017-10-05 17:40:31 +02:00
```
Removing players with *either* the first name of Sam or the last name of Minnée requires multiple `->exclude` calls:
2017-08-03 05:35:09 +02:00
2017-10-05 17:40:31 +02:00
```php
$players = Player::get()->exclude('FirstName', 'Sam')->exclude('Surname', 'Minnée');
// SELECT * FROM Player WHERE FirstName != 'Sam' AND LastName != 'Minnée'
```
2014-10-27 04:40:02 +01:00
And removing Sig and Sam with that are either age 17 or 43.
2014-10-27 04:40:02 +01:00
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->exclude([
'FirstName' => ['Sam', 'Sig'],
'Age' => [17, 43]
]);
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
// SELECT * FROM Player WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '43'));
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
You can use [SearchFilters](searchfilters) to add additional behavior to your `exclude` command.
```php
2017-08-07 05:11:17 +02:00
$players = Player::get()->exclude([
'FirstName:EndsWith' => 'S'
'PlayerNumber:LessThanOrEqual' => '10'
]);
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
### Subtract
You can subtract entries from a [DataList](api:SilverStripe\ORM\DataList) by passing in another DataList to `subtract()`
2014-10-27 04:40:02 +01:00
```php
2017-08-07 05:11:17 +02:00
$sam = Player::get()->filter('FirstName', 'Sam');
$players = Player::get();
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
$noSams = $players->subtract($sam);
```
2014-10-27 04:40:02 +01:00
Though for the above example it would probably be easier to use `filter()` and `exclude()`. A better use case could be
when you want to find all the members that does not exist in a Group.
```php
2017-08-07 05:11:17 +02:00
// ... Finding all members that does not belong to $group.
2017-10-26 02:22:02 +02:00
use SilverStripe\Security\Member;
2017-08-07 05:11:17 +02:00
$otherMembers = Member::get()->subtract($group->Members());
```
2014-10-27 04:40:02 +01:00
### Limit
You can limit the amount of records returned in a DataList by using the `limit()` method.
```php
2017-10-26 02:22:02 +02:00
use SilverStripe\Security\Member;
2017-08-07 05:11:17 +02:00
$members = Member::get()->limit(5);
```
2014-10-27 04:40:02 +01:00
`limit()` accepts two arguments, the first being the amount of results you want returned, with an optional second
parameter to specify the offset, which allows you to tell the system where to start getting the results from. The
offset, if not provided as an argument, will default to 0.
```php
2017-08-07 05:11:17 +02:00
// Return 10 members with an offset of 4 (starting from the 5th result).
$members = Member::get()->sort('Surname')->limit(10, 4);
```
2014-10-27 04:40:02 +01:00
<div class="alert">
Note that the `limit` argument order is different from a MySQL LIMIT clause.
</div>
### Mapping classes to tables with DataObjectSchema
Note that in most cases, the underlying database table for any DataObject instance will be the same as the class name.
However in cases where dealing with namespaced classes, especially when using DB schema which don't support
slashes in table names, it is necessary to provide an alternate mapping.
For instance, the below model will be stored in the table name `BannerImage`
```php
2017-08-07 05:11:17 +02:00
namespace SilverStripe\BannerManager;
2017-10-26 02:22:02 +02:00
use SilverStripe\ORM\DataObject;
2017-08-07 05:11:17 +02:00
class BannerImage extends \DataObject
{
private static $table_name = 'BannerImage';
}
```
Note that any model class which does not explicitly declare a `table_name` config option will have a name
automatically generated for them. In the above case, the table name would have been
`SilverStripe\BannerManager\BannerImage`
When creating raw SQL queries that contain table names, it is necessary to ensure your queries have the correct
table. This functionality can be provided by the [DataObjectSchema](api:SilverStripe\ORM\DataObjectSchema) service, which can be accessed via
`DataObject::getSchema()`. This service provides the following methods, most of which have a table and class
equivalent version.
Methods which return class names:
* `tableClass($table)` Finds the class name for a given table. This also handles suffixed tables such as `Table_Live`.
* `baseDataClass($class)` Returns the base data class for the given class.
* `classForField($class, $field)` Finds the specific class that directly holds the given field
Methods which return table names:
* `tableName($class)` Returns the table name for a given class or object.
* `baseDataTable($class)` Returns the base data class for the given class.
* `tableForField($class, $field)` Finds the specific class that directly holds the given field and returns the table.
Note that in cases where the class name is required, an instance of the object may be substituted.
For example, if running a query against a particular model, you will need to ensure you use the correct
table and column.
```php
2017-10-26 02:22:02 +02:00
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\DataObject;
2017-08-07 05:11:17 +02:00
public function countDuplicates($model, $fieldToCheck)
{
$table = DataObject::getSchema()->tableForField($model, $field);
$query = new SQLSelect();
$query->setFrom("\"{$table}\"");
$query->setWhere(["\"{$table}\".\"{$field}\"" => $model->$fieldToCheck]);
return $query->count();
}
```
2014-10-27 04:40:02 +01:00
### Raw SQL
Occasionally, the system described above won't let you do exactly what you need to do. In these situations, we have
2014-10-28 04:45:46 +01:00
methods that manipulate the SQL query at a lower level. When using these, please ensure that all table and field names
are escaped with double quotes, otherwise some DB backends (e.g. PostgreSQL) won't work.
2014-10-27 04:40:02 +01:00
Under the hood, query generation is handled by the [DataQuery](api:SilverStripe\ORM\DataQuery) class. This class does provide more direct access
2014-10-27 04:40:02 +01:00
to certain SQL features that `DataList` abstracts away from you.
In general, we advise against using these methods unless it's absolutely necessary. If the ORM doesn't do quite what
you need it to, you may also consider extending the ORM with new data types or filter modifiers
#### Where clauses
You can specify a WHERE clause fragment (that will be combined with other filters using AND) with the `where()` method:
2017-10-26 02:22:02 +02:00
```php
2017-08-07 05:11:17 +02:00
$members = Member::get()->where("\"FirstName\" = 'Sam'")
```
2014-10-27 04:40:02 +01:00
#### Joining Tables
You can specify a join with the `innerJoin` and `leftJoin` methods. Both of these methods have the same arguments:
* The name of the table to join to.
* The filter clause for the join.
* An optional alias.
```php
2017-08-07 05:11:17 +02:00
// Without an alias
$members = Member::get()
->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
$members = Member::get()
->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "Rel");
```
2014-10-27 04:40:02 +01:00
<div class="alert" markdown="1">
Passing a *$join* statement to will filter results further by the JOINs performed against the foreign table. It will
**not** return the additionally joined data.
</div>
### Default Values
Define the default values for all the `$db` fields. This example sets the "Status"-column on Player to "Active"
whenever a new object is created.
```php
2017-08-07 05:11:17 +02:00
use SilverStripe\ORM\DataObject;
2017-08-05 00:45:24 +02:00
2017-08-07 05:11:17 +02:00
class Player extends DataObject
{
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
private static $defaults = [
"Status" => 'Active',
];
}
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
<div class="notice" markdown='1'>
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
2016-01-14 11:59:53 +01:00
[Data Types and Casting](/developer_guides/model/data_types_and_casting) for details.
2014-10-27 04:40:02 +01:00
</div>
## Subclasses
Inheritance is supported in the data model: separate tables will be linked together, the data spread across these
tables. The mapping and saving logic is handled by SilverStripe, you don't need to worry about writing SQL most of the
time.
For example, suppose we have the following set of classes:
```php
2017-08-07 05:11:17 +02:00
use SilverStripe\CMS\Model\SiteTree;
use Page;
2017-08-05 00:45:24 +02:00
2017-08-07 05:11:17 +02:00
class Page extends SiteTree
{
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
}
class NewsPage extends Page
{
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
private static $db = [
'Summary' => 'Text'
];
}
2017-08-03 05:35:09 +02:00
```
2014-10-27 04:40:02 +01:00
The data for the following classes would be stored across the following tables:
```yml
2017-10-26 02:22:02 +02:00
SilverStripe\CMS\Model\SiteTree:
2017-08-07 05:11:17 +02:00
- ID: Int
- ClassName: Enum('SiteTree', 'Page', 'NewsPage')
- Created: Datetime
- LastEdited: Datetime
- Title: Varchar
- Content: Text
NewsPage:
- ID: Int
- Summary: Text
```
2014-10-27 04:40:02 +01:00
Accessing the data is transparent to the developer.
```php
2017-08-07 05:11:17 +02:00
$news = NewsPage::get();
2014-10-27 04:40:02 +01:00
2017-08-07 05:11:17 +02:00
foreach($news as $article) {
echo $article->Title;
}
```
2014-10-27 04:40:02 +01:00
The way the ORM stores the data is this:
* "Base classes" are direct sub-classes of [DataObject](api:SilverStripe\ORM\DataObject). They are always given a table, whether or not they have
2014-10-27 04:40:02 +01:00
special fields. This is called the "base table". In our case, `SiteTree` is the base table.
* The base table's ClassName field is set to class of the given record. It's an enumeration of all possible
sub-classes of the base class (including the base class itself).
* Each sub-class of the base object will also be given its own table, *as long as it has custom fields*. In the
2014-10-28 04:45:46 +01:00
example above, NewsSection didn't have its own data, so an extra table would be redundant.
2014-10-27 04:40:02 +01:00
* In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record:
record #2 in Page refers to the same object as record #2 in [SiteTree](api:SilverStripe\CMS\Model\SiteTree).
2014-10-27 04:40:02 +01:00
To retrieve a news article, SilverStripe joins the [SiteTree](api:SilverStripe\CMS\Model\SiteTree), [Page](api:SilverStripe\CMS\Model\SiteTree\Page) and NewsPage tables by their ID fields.
2014-10-27 04:40:02 +01:00
## Related Documentation
2016-01-14 11:59:53 +01:00
* [Data Types and Casting](/developer_guides/model/data_types_and_casting)
2014-10-27 04:40:02 +01:00
## API Documentation
* [DataObject](api:SilverStripe\ORM\DataObject)
* [DataList](api:SilverStripe\ORM\DataList)
* [DataQuery](api:SilverStripe\ORM\DataQuery)
* [DataObjectSchema](api:SilverStripe\ORM\DataObjectSchema)