# Datamodel SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) that assumes the following connections: * 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 All data tables in SilverStripe are defined as subclasses of `[api:DataObject]`. Inheritance is supported in the data model: seperate tables will be linked together, the data spread across these tables. The mapping and saving/loading logic is handled by sapphire, you don't need to worry about writing SQL most of the time. The advanced object-relational layer in SilverStripe is one of the main reasons for requiring PHP5. Most of its customizations are possible through [PHP5 Object Overloading](http://www.onlamp.com/pub/a/php/2005/06/16/overloading.html) handled in the `[api:Object]`-class. See [database-structure](/reference/database-structure) for in-depth information on the database-schema. ## Generating the database-schema The SilverStripe database-schema is generated automatically by visiting the URL. `http:///dev/build`
Note: You need to be logged in as an administrator to perform this command.
## Querying Data Every query to data starts with a `DataList::create($class)` call. For example, this query would return all of the Member objects: :::php $members = DataList::create('Member'); The ORM uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods are filter() and sort(): :::php $members = DataList::create('Member')->filter(array('FirstName' => 'Sam'))->sort('Surname'); Those of you who know a bit about SQL might be thinking "it looks like you're querying all members, and then filtering to those with a first name of 'Sam'. Isn't this very slow?" Is isn't, because the ORM doesn't actually execute the query until you iterate on the result with a `foreach()` or `<% control %>`. :::php // The SQL query isn't executed here... $members = DataList::create('Member'); // ...or here $members = $members->filter(array('FirstName' => 'Sam')); // ...or even here $members = $members->sort('Surname'); // *This* is where the query is executed foreach($members as $member) { echo "

$member->FirstName $member->Surname

"; } This also means that getting the count of a list of objects will be done with a single, efficient query. :::php $members = DataList::create('Member')->filter(array('FirstName' => 'Sam'))->sort('Surname'); // This will create an single SELECT COUNT query. echo $members->Count(); All of this lets you focus on writing your application, and not worrying too much about whether or not your queries are efficient. ### 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 $member = DataList::create('Member')->byID(5); If you have constructed a query that you know should return a single record, you can call `First()`: :::php $member = DataList::create('Member')->filter(array('FirstName' => 'Sam', 'Surname' => 'Minnee'))->First(); ### Sort Quiet often you would like to sort a list. Doing this on a list could be done in a few ways. If would like to sort the list by FirstName in a ascending way (from A to Z). :::php $member = DataList::create('Member')->sort('FirstName'); // Or the more expressive way $member = DataList::create('Member')->sort('FirstName', 'ASC'); To reverse the sort :::php $member = DataList::create('Member')->sort('FirstName', 'DESC'); However you might have several entries with the same FirstName and would like to sort them by FirstName and LastName :::php $member = DataList::create('Member')->sort(array( 'FirstName' => 'ASC', 'LastName'=>'ASC' )); ### Filter As you might expect, the `filter()` method filters the list of objects that gets returned. The previous example included this filter, which returns all Members with a first name of "Sam". :::php $members = DataList::create('Member')->filter(array('FirstName' => 'Sam')); In SilverStripe 2, we would have passed `"\"FirstName\" = 'Sam'` to make this query. Now, we pass an array, `array('FirstName' => 'Sam')`, to minimise the risk of SQL injection bugs. The format of this array follows a few rules: * Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must be true. * The key in the filter corresponds to the field that you want to filter by. * The value in the filter corresponds to the value that you want to filter to. So, this would return only those members called "Sam Minnée". :::php $members = DataList::create('Member')->filter(array( 'FirstName' => 'Sam', 'Surname' => 'Minnée', )); There are also a short hand way of getting Members with the FirstName of Sam. :::php $members = DataList::create('Member')->filter('FirstName', 'Sam'); Or if you want to find both Sam and Sig. :::php $members = DataList::create('Member')->filter( 'FirstName', array('Sam', 'Sig') ); Then there is the most complex task when you want to find Sam and Sig that has either Age 17 or 74. :::php $members = DataList::create('Member')->filter(array( 'FirstName' => array('Sam', 'Sig'), 'Age' => array(17, 74) )); This would be equivalent to a SQL query of ::: ... WHERE ("FirstName" IN ('Sam', 'Sig) AND "Age" IN ('17', '74)); ### Exclude The `exclude()` method is the opposite to the filter in that it removes entries from a list. If we would like to remove all members from the list with the FirstName of Sam. :::php $members = DataList::create('Member')->exclude('FirstName', 'Sam'); Remove both Sam and Sig is as easy as. :::php $members = DataList::create('Member')->exclude('FirstName', array('Sam','Sig')); As you can see it follows the same pattern as filter, so for removing only Sam Minnée from the list :::php $members = DataList::create('Member')->exclude(array( 'FirstName' => 'Sam', 'Surname' => 'Minnée', )); And removing Sig and Sam with that are either age 17 or 74. :::php $members = DataList::create('Member')->exclude(array( 'FirstName' => array('Sam', 'Sig'), 'Age' => array(17, 43) )); This would be equivalent to a SQL query of ::: ... WHERE ("FirstName" NOT IN ('Sam','Sig) OR "Age" NOT IN ('17', '74)); **FUN FACT:** The functionality below isn't implemented in the code yet. By default, these filters specify case-insensitive exact matches. There are a number of suffixes that you can put on field names to change this: `":StartsWith"`, `":EndsWith"`, `":PartialMatch"`, `":GreaterThan"`, `":LessThan"`, `":Negation"`. This query will return everyone whose first name doesn't start with S, who have logged on since 1/1/2011. :::php $members = DataList::create('Member')->filter(array( 'FirstName:StartsWith:Not' => 'S' 'LastVisited:GreaterThan' => '2011-01-01' )); If you wish to match against any of a number of columns, you can list several field names, separated by commas. This will return all members whose first name or surname contain the string 'sam'. :::php $members = DataList::create('Member')->filter(array( 'FirstName,Surname:Contains' => 'sam' )); If you wish to match against any of a number of values, you can pass an array as the value. This will return all members whose first name is either Sam or Ingo. :::php $members = DataList::create('Member')->filter(array( 'FirstName' => array('sam', 'ingo'), )); ### Relation filters So far we have only filtered a data list by fields on the object that you're requesting. For simple cases, this might be okay, but often, a data model is made up of a number of related objects. For example, in SilverStripe each member can be placed in a number of groups, and each group has a number of permissions. For this, Sapphire ORM supports **Relation Filters**. Any ORM request can be filtered by fields on a related object by specifying the filter key as `.`. You can chain relations together as many times as is necessary. For example, this will return all members assigned ot a group that has a permission record with the code "ADMIN". In other words, it will return all administrators. :::php $members = DataList::create('Member')->filter(array( 'Groups.Permissions.Code' => 'ADMIN', )); Note that we are just joining to these tables to filter the records. Even if a member is in more than 1 administrator group, unique members will still be returned by this query. The other features of filters can be applied to relation filters as well. This will return all members in groups whose names start with 'A' or 'B'. :::php $members = DataList::create('Member')->filter(array( 'Groups.Title:StartsWith' => array('A', 'B'), )); You can even follow a relation back to the original model class! This will return all members are in at least 1 group that also has a member called Sam. :::php $members = DataList::create('Member')->filter(array( 'Groups.Members.FirstName' => 'Sam' )); ### Raw SQL options for advanced users Occassionally, the system described above won't let you do exactly what you need to do. In these situtations, we have methods that manipulate the SQL query at a lower level. When using these, please ensure that all table & field names are escaped with double quotes, otherwise some DB back-ends (e.g. PostgreSQL) won't work. 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 (that documentation still needs to be written) #### Where clauses You can specify a WHERE clause fragment (that will be combined with other filters using AND) with the `where()` method: :: php $members = DataList::create('Member')->where("\"FirstName\" = 'Sam'") #### Joining 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 For example: :: php // Without an alias $members = DataList::create('Member')->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\""); $members = DataList::create('Member')->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "REl"); Passing a *$join* statement to DataObject::get will filter results further by the JOINs performed against the foreign table. **It will NOT return the additionally joined data.** The returned *$records* will always be a `[api:DataObject]`. ## Properties ### Definition Data is defined in the static variable $db on each class, in the format: `` => "data-type" :::php class Player extends DataObject { static $db = array( "FirstName" => "Varchar", "Surname" => "Varchar", "Description" => "Text", "Status" => "Enum('Active, Injured, Retired')", "Birthday" => "Date" ); } See [data-types](data-types) for all available types. ### Overloading "Getters" and "Setters" are functions that help us save fields to our data objects. By default, the methods getField() and setField() are used to set data object fields. They save to the protected array, $obj->record. We can overload the default behaviour by making a function called "get``" or "set``". :::php class Player extends DataObject { static $db = array( "Status" => "Enum('Active, Injured, Retired')" ); // access through $myPlayer->Status function getStatus() { // check if the Player is actually... born already! return (!$this->obj("Birthday")->InPast()) ? "Unborn" : $this->Status; } ### Customizing We can create new "virtual properties" which are not actually listed in *static $db* or stored in the database-row. Here we combined a Player's first name and surname, accessible through $myPlayer->Title. :::php class Player extends DataObject { function getTitle() { return "{$this->FirstName} {$this->Surname}"; } // access through $myPlayer->Title = "John Doe"; // just saves data on the object, please use $myPlayer->write() to save the database-row function setTitle($title) { list($firstName, $surName) = explode(' ', $title); $this->FirstName = $firstName; $this->Surname = $surName; } }
**CAUTION:** It is common practice to make sure that pairs of custom getters/setter deal with the same data, in a consistent format.
**CAUTION:** Custom setters can be hard to debug: Please double check if you could transform your data in more straight-forward logic embedded to your custom controller or form-saving.
### 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 class Player extends DataObject { static $defaults = array( "Status" => 'Active', ); }
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See [data-types](data-types) for details.
### Casting Properties defined in *static $db* are automatically casted to their [data-types](data-types) when used in templates. You can also cast the return-values of your custom functions (e.g. your "virtual properties"). Calling those functions directly will still return whatever type your php-code generates, but using the *obj()*-method or accessing through a template will cast the value according to the $casting-definition. :::php class Player extends DataObject { static $casting = array( "MembershipFee" => 'Currency', ); // $myPlayer->MembershipFee() returns a float (e.g. 123.45) // $myPlayer->obj('MembershipFee') returns a object of type Currency // In a template: <% control MyPlayer %>MembershipFee.Nice<% end_control %> returns a casted string (e.g. "$123.45") function getMembershipFee() { return $this->Team()->BaseFee * $this->MembershipYears; } } ## Relations Relations are built through static array definitions on a class, in the format ` => ` ### has_one A 1-to-1 relation creates a database-column called "``ID", in the example below this would be "TeamID" on the "Player"-table. :::php // access with $myPlayer->Team() class Player extends DataObject { static $has_one = array( "Team" => "Team", ); } SilverStripe's `[api:SiteTree]` base-class for content-pages uses a 1-to-1 relationship to link to its parent element in the tree: :::php // access with $mySiteTree->Parent() class SiteTree extends DataObject { static $has_one = array( "Parent" => "SiteTree", ); } ### has_many Defines 1-to-many joins. A database-column named ""``ID"" will to be created in the child-class.
**CAUTION:** Please specify a $has_one-relationship on the related child-class as well, in order to have the necessary accessors available on both ends.
:::php // access with $myTeam->Players() or $player->Team() class Team extends DataObject { static $has_many = array( "Players" => "Player", ); } class Player extends DataObject { static $has_one = array( "Team" => "Team", ); } To specify multiple $has_manys to the same object you can use dot notation to distinguish them like below :::php class Person { static $has_many = array( "Managing" => "Company.Manager", "Cleaning" => "Company.Cleaner", ); } class Company { static $has_one = array( "Manager" => "Person", "Cleaner" => "Person" ); } Multiple $has_one relationships are okay if they aren't linking to the same object type. :::php /** * THIS IS BAD */ class Team extends DataObject { static $has_many = array( "Players" => "Player", ); } class Player extends DataObject { static $has_one = array( "Team" => "Team", "AnotherTeam" => "Team", ); } ### many_many Defines many-to-many joins. A new table, (this-class)_(relationship-name), will be created with a pair of ID fields.
**CAUTION:** Please specify a $belongs_many_many-relationship on the related class as well, in order to have the necessary accessors available on both ends.
:::php // access with $myTeam->Categories() or $myCategory->Teams() class Team extends DataObject { static $many_many = array( "Categories" => "Category", ); } class Category extends DataObject { static $belongs_many_many = array( "Teams" => "Team", ); } ### Adding relations Inside sapphire it doesn't matter if you're editing a *has_many*- or a *many_many*-relationship. You need to get a `[api:ComponentSet]`. :::php class Team extends DataObject { // see "many_many"-description for a sample definition of class "Category" static $many_many = array( "Categories" => "Category", ); /** * @param DataObjectSet */ function addCategories($additionalCategories) { $existingCategories = $this->Categories(); // method 1: Add many by iteration foreach($additionalCategories as $category) { $existingCategories->add($category); } // method 2: Add many by ID-List $existingCategories->addMany(array(1,2,45,745)); } } ### Custom Relations You can use the flexible datamodel to get a filtered result-list without writing any SQL. For example, this snippet gets you the "Players"-relation on a team, but only containing active players. (See `[api:DataObject::$has_many]` for more info on the described relations). :::php class Team extends DataObject { static $has_many = array( "Players" => "Player" ); // can be accessed by $myTeam->ActivePlayers() function ActivePlayers() { return $this->Players("Status='Active'"); } } ## Maps A map is an array where the array indexes contain data as well as the values. You can build a map from any DataList like this: :::php $members = DataList::create('Member')->map('ID', 'FirstName'); This will return a map where the keys are Member IDs, and the values are the corresponding FirstName values. Like everything else in the ORM, these maps are lazy loaded, so the following code will only query a single record from the database: :::php $members = DataList::create('Member')->map('ID', 'FirstName'); echo $member[5]; This functionality is provided by the `SS_Map` class, which can be used to build a map around any `SS_List`. :::php $members = DataList::create('Member'); $map = new SS_Map($members, 'ID', 'FirstName'); ## Data Handling When saving data through the object model, you don't have to manually escape strings to create SQL-safe commands. You have to make sure though that certain properties are not overwritten, e.g. *ID* or *ClassName*. ### Creation :::php $myPlayer = new Player(); $myPlayer->Firstname = "John"; // sets property on object $myPlayer->write(); // writes row to database ### Update :::php $myPlayer = DataObject::get_by_id('Player',99); if($myPlayer) { $myPlayer->Firstname = "John"; // sets property on object $myPlayer->write(); // writes row to database } ### Batch Update :::php $myPlayer->update( ArrayLib::filter_keys( $_REQUEST, array('Birthday', 'Firstname') ) ); Alternatively you can use *castedUpdate()* to respect the [data-types](/topics/data-types). This is preferred to manually casting data before saving. :::php $myPlayer->castedUpdate( ArrayLib::filter_keys( $_REQUEST, array('Birthday', 'Firstname') ) ); ### onBeforeWrite You can customize saving-behaviour for each DataObject, e.g. for adding security. These functions are private, obviously it wouldn't make sense to call them externally on the object. They are triggered when calling *write()*. Example: Disallow creation of new players if the currently logged-in player is not a team-manager. :::php class Player extends DataObject { static $has_many = array( "Teams"=>"Team" ); function onBeforeWrite() { // check on first write action, aka "database row creation" (ID-property is not set) if(!$this->ID) { $currentPlayer = Member::currentUser(); if(!$currentPlayer->IsTeamManager()) { user_error('Player-creation not allowed', E_USER_ERROR); exit(); } } // check on every write action if(!$this->record['TeamID']) { user_error('Cannot save player without a valid team-connection', E_USER_ERROR); exit(); } // CAUTION: You are required to call the parent-function, otherwise sapphire will not execute the request. parent::onBeforeWrite(); } }
Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*. Please check for the existence of $this->ID to toggle these two modes, as shown in the example above.
### onBeforeDelete Triggered before executing *delete()* on an existing object. Example: Checking for a specific [permission](/reference/permission) to delete this type of object. It checks if a member is logged in who belongs to a group containing the permission "PLAYER_DELETE". :::php class Player extends DataObject { static $has_many = array( "Teams"=>"Team" ); function onBeforeDelete() { if(!Permission::check('PLAYER_DELETE')) { Security::permissionFailure($this); exit(); } parent::onBeforeDelete(); } } ### Saving data with forms See [forms](/topics/forms). ### Saving data with custom SQL See `[api:SQLQuery]` for custom *INSERT*, *UPDATE*, *DELETE* queries. ## Extending DataObjects You can add properties and methods to existing `[api:DataObjects]`s like `[api:Member]` (a core class) without hacking core code or subclassing. Please see `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for our most popular examples. ## FAQ ### Whats the difference between DataObject::get() and a relation-getter? You can work with both in pretty much the same way, but relationship-getters return a special type of collection: A `[api:ComponentSet]` with relation-specific functionality. :::php $myTeam = DataObject::get_by_id('Team',$myPlayer->TeamID); // returns DataObject $myTeam->add(new Player()); // fails $myTeam = $myPlayer->Team(); // returns Componentset $myTeam->add(new Player()); // works