2011-02-07 07:48:44 +01:00
# Datamodel
SilverStripe uses an [object-relational model ](http://en.wikipedia.org/wiki/Object-relational_model ) that assumes the
following connections:
2012-06-23 00:32:43 +02: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
2011-02-07 07:48:44 +01:00
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
2012-03-24 22:16:59 +01:00
logic is handled by SilverStripe, you don't need to worry about writing SQL most of the time.
2011-02-07 07:48:44 +01:00
2012-06-23 00:32:43 +02:00
Most of the ORM customizations are possible through [PHP5 Object
2011-02-07 07:48:44 +01:00
Overloading](http://www.onlamp.com/pub/a/php/2005/06/16/overloading.html) handled in the `[api:Object]` -class.
2012-06-28 14:51:04 +02:00
See [database-structure ](/reference/database-structure ) for in-depth information on the database-schema,
and the ["sql queries" topic ](/reference/sqlquery ) in case you need to drop down to the bare metal.
2011-02-07 07:48:44 +01:00
2012-06-23 00:32:43 +02:00
## Generating the Database Schema
2011-02-07 07:48:44 +01:00
The SilverStripe database-schema is generated automatically by visiting the URL.
2011-03-08 22:05:51 +01:00
`http://<mysite>/dev/build`
2011-02-07 07:48:44 +01:00
2011-03-08 22:05:51 +01:00
< div class = "notice" markdown = '1' >
Note: You need to be logged in as an administrator to perform this command.
< / div >
2011-02-07 07:48:44 +01:00
## Querying Data
2012-06-23 00:32:43 +02:00
Every query to data starts with a `DataList::create(<class>)` or `<class>::get()` call. For example, this query would return all of the `Member` objects:
2011-02-07 07:48:44 +01:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get();
2011-04-04 02:18:50 +02:00
The ORM uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods
2012-06-23 00:32:43 +02:00
are `filter()` and `sort()` :
2011-02-07 07:48:44 +01:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array('FirstName' => 'Sam'))->sort('Surname');
2011-04-04 02:18:50 +02:00
Those of you who know a bit about SQL might be thinking "it looks like you're querying all members, and then filtering
2012-08-03 07:00:43 +02:00
to those with a first name of 'Sam'. Isn't this very slow?" Is isn't, because the ORM doesn't actually execute the SQL
query until you iterate on the result with a `foreach()` or `<% loop %>` . The ORM is smart enough to generate a single
efficient query at the last moment in time without needing to post process the result set in PHP. In MySQL the query
generated by the ORM may look something like this for the previous query.
:::
SELECT * FROM Member WHERE FirstName = 'Sam' ORDER BY Surname
An example of the query process in action:
2011-02-07 07:48:44 +01:00
:::php
2011-04-04 02:18:50 +02:00
// The SQL query isn't executed here...
2012-05-09 01:29:24 +02:00
$members = Member::get();
2011-04-04 02:18:50 +02:00
// ...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 "< p > $member->FirstName $member->Surname< / p > ";
}
2011-02-07 07:48:44 +01:00
2011-04-04 02:18:50 +02:00
This also means that getting the count of a list of objects will be done with a single, efficient query.
2011-02-07 07:48:44 +01:00
2011-04-04 02:18:50 +02:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array('FirstName' => 'Sam'))->sort('Surname');
2012-08-03 07:00:43 +02:00
// This will create an single SELECT COUNT query similar to -
// SELECT COUNT(*) FROM Members WHERE FirstName = 'Sam'
2011-04-04 02:18:50 +02:00
echo $members->Count();
2011-02-07 07:48:44 +01:00
2011-04-04 02:18:50 +02:00
### Returning a single DataObject
2012-08-03 07:00:43 +02:00
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)` :
2011-04-04 02:18:50 +02:00
:::php
2012-05-09 01:29:24 +02:00
$member = Member::get()->byID(5);
2011-02-07 07:48:44 +01:00
2011-04-04 02:18:50 +02:00
If you have constructed a query that you know should return a single record, you can call `First()` :
2011-02-07 07:48:44 +01:00
:::php
2012-05-09 01:29:24 +02:00
$member = Member::get()->filter(array('FirstName' => 'Sam', 'Surname' => 'Minnee'))->First();
2011-04-04 02:18:50 +02:00
2011-12-08 22:09:48 +01:00
### Sort
2011-04-04 02:18:50 +02:00
2012-08-03 07:00:43 +02:00
Quite often you would like to sort a list. Doing this on a list could be done in a few ways.
2011-12-08 22:09:48 +01:00
2012-06-23 00:32:43 +02:00
If would like to sort the list by `FirstName` in a ascending way (from A to Z).
2011-12-08 22:09:48 +01:00
:::php
2012-08-03 07:00:43 +02:00
$member = Member::get()->sort('FirstName', 'ASC'); // ASC or DESC
2012-06-23 00:32:43 +02:00
$member = Member::get()->sort('FirstName'); // Ascending is implied
2011-12-08 22:09:48 +01:00
To reverse the sort
:::php
2012-05-09 01:29:24 +02:00
$member = Member::get()->sort('FirstName', 'DESC');
2011-12-08 22:09:48 +01:00
2012-08-03 07:00:43 +02:00
However you might have several entries with the same `FirstName` and would like to sort them by `FirstName`
and `LastName`
2011-12-08 22:09:48 +01:00
:::php
2012-05-09 01:29:24 +02:00
$member = Member::get()->sort(array(
2011-12-08 22:09:48 +01:00
'FirstName' => 'ASC',
'LastName'=>'ASC'
));
2012-08-03 07:00:43 +02:00
You can also sort randomly
:::php
$member = Member::get()->sort('RAND()')
2011-12-08 22:09:48 +01:00
### Filter
2011-04-04 02:18:50 +02:00
2012-08-03 07:00:43 +02:00
As you might expect, the `filter()` method filters the list of objects that gets returned. The previous example
2011-04-04 02:18:50 +02:00
included this filter, which returns all Members with a first name of "Sam".
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array('FirstName' => 'Sam'));
2011-04-04 02:18:50 +02:00
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:
2012-08-03 07:00:43 +02:00
* Each element of the array specifies a filter. You can specify as many filters as you like, and they **all** must
be true.
2011-04-04 02:18:50 +02:00
* 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
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array(
2011-04-04 02:18:50 +02:00
'FirstName' => 'Sam',
'Surname' => 'Minnée',
));
2012-08-03 07:00:43 +02:00
There is also a short hand way of getting Members with the FirstName of Sam.
2011-12-08 22:09:48 +01:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter('FirstName', 'Sam');
2011-12-08 22:09:48 +01:00
Or if you want to find both Sam and Sig.
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(
2011-12-08 22:09:48 +01:00
'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
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array(
2011-12-08 22:09:48 +01:00
'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
2012-05-09 01:29:24 +02:00
$members = Member::get()->exclude('FirstName', 'Sam');
2011-12-08 22:09:48 +01:00
Remove both Sam and Sig is as easy as.
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->exclude('FirstName', array('Sam','Sig'));
2011-12-08 22:09:48 +01:00
As you can see it follows the same pattern as filter, so for removing only Sam Minnée from the list
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->exclude(array(
2011-12-08 22:09:48 +01:00
'FirstName' => 'Sam',
'Surname' => 'Minnée',
));
And removing Sig and Sam with that are either age 17 or 74.
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->exclude(array(
2011-12-08 22:09:48 +01:00
'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));
2012-08-03 07:00:43 +02:00
### Search Filter Modifiers
The where clauses showcased in the previous two sections (filter and exclude) specify case-insensitive exact
matches by default. However, there are a number of suffixes that you can put on field names to change this
behaviour `":StartsWith"` , `":EndsWith"` , `":PartialMatch"` , `":GreaterThan"` , `":LessThan"` , `":Negation"` .
Each of these suffixes is represented in the ORM as a subclass of `[api:SearchFilter]` . Developers can define
their own SearchFilters if needing to extend the ORM filter and exclude behaviours.
2011-04-04 02:18:50 +02:00
2012-08-03 07:00:43 +02:00
The following is a query which will return everyone whose first name doesn't start with S, who has logged in
since 1/1/2011.
2011-04-04 02:18:50 +02:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array(
2011-04-04 02:18:50 +02:00
'FirstName:StartsWith:Not' => 'S'
'LastVisited:GreaterThan' => '2011-01-01'
));
2012-08-03 07:00:43 +02:00
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'.
2011-04-04 02:18:50 +02:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array(
2012-07-11 09:18:27 +02:00
'FirstName,Surname:PartialMatch' => 'sam'
2011-04-04 02:18:50 +02:00
));
2012-01-25 23:53:12 +01:00
### Subtract
You can subtract entries from a DataList by passing in another DataList to `subtract()`
:::php
2012-05-09 01:29:24 +02:00
$allSams = Member::get()->filter('FirstName', 'Sam');
$allMembers = Member::get();
2012-01-25 23:53:12 +01:00
$noSams = $allMembers->subtract($allSams);
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
// ... Finding all members that does not belong to $group.
2012-05-09 01:29:24 +02:00
$otherMembers = Member::get()->subtract($group->Members());
2012-01-25 23:53:12 +01:00
2011-04-04 02:18:50 +02:00
### 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.
2012-08-03 07:00:43 +02:00
For this, the SilverStripe ORM supports **Relation Filters** . Any ORM request can be filtered by fields on a related
object by specifying the filter key as `<relation-name>.<field-in-related-object>` . You can chain relations together
as many times as is necessary.
2011-04-04 02:18:50 +02:00
2012-08-03 07:00:43 +02:00
For example, this will return all members assigned to a group that has a permission record with the code "ADMIN". In
other words, it will return all administrators.
2011-04-04 02:18:50 +02:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array(
2011-04-04 02:18:50 +02:00
'Groups.Permissions.Code' => 'ADMIN',
));
2012-08-03 07:00:43 +02:00
Note that we are just joining 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.
2011-04-04 02:18:50 +02:00
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
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array(
2011-04-04 02:18:50 +02:00
'Groups.Title:StartsWith' => array('A', 'B'),
));
2012-08-03 07:00:43 +02:00
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.
2011-04-04 02:18:50 +02:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->filter(array(
2011-04-04 02:18:50 +02:00
'Groups.Members.FirstName' => 'Sam'
));
### Raw SQL options for advanced users
2012-05-09 01:29:24 +02:00
Occasionally, the system described above won't let you do exactly what you need to do. In these situtations, we have
2011-04-04 02:18:50 +02:00
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
2012-08-03 07:00:43 +02:00
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)
2011-04-04 02:18:50 +02:00
#### Where clauses
You can specify a WHERE clause fragment (that will be combined with other filters using AND) with the `where()` method:
2012-08-03 07:00:43 +02:00
:::php
2012-05-09 01:29:24 +02:00
$members = Member::get()->where("\"FirstName\" = 'Sam'")
2011-04-04 02:18:50 +02:00
#### 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:
2012-08-03 07:00:43 +02:00
:::php
2011-04-04 02:18:50 +02:00
// Without an alias
2012-05-09 01:29:24 +02:00
$members = Member::get()->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
2011-04-04 02:18:50 +02:00
2012-05-09 01:29:24 +02:00
$members = Member::get()->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "REl");
2011-04-04 02:18:50 +02:00
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]` .
2011-02-07 07:48:44 +01:00
## Properties
### Definition
Data is defined in the static variable $db on each class, in the format:
`<property-name>` => "data-type"
:::php
class Player extends DataObject {
2012-08-03 07:00:43 +02:00
public static $db = array(
2011-02-07 07:48:44 +01:00
"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`< fieldname > `" or "set`< fieldname > `".
:::php
class Player extends DataObject {
2012-08-03 07:00:43 +02:00
public static $db = array(
2011-02-07 07:48:44 +01:00
"Status" => "Enum('Active, Injured, Retired')"
);
// access through $myPlayer->Status
2012-01-30 23:13:42 +01:00
public function getStatus() {
2011-02-07 07:48:44 +01:00
// 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 {
2012-01-30 23:13:42 +01:00
public function getTitle() {
2011-02-07 07:48:44 +01:00
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
2012-01-30 23:13:42 +01:00
public function setTitle($title) {
2011-02-07 07:48:44 +01:00
list($firstName, $surName) = explode(' ', $title);
$this->FirstName = $firstName;
$this->Surname = $surName;
}
}
2011-03-08 22:05:51 +01:00
< div class = "warning" markdown = '1' >
**CAUTION:** It is common practice to make sure that pairs of custom getters/setter deal with the same data, in a consistent
format.
< / div >
2011-02-07 07:48:44 +01:00
2011-03-08 22:05:51 +01:00
< div class = "warning" markdown = '1' >
**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.
< / div >
2011-02-07 07:48:44 +01:00
### 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 {
2012-08-03 07:00:43 +02:00
public static $defaults = array(
2011-02-07 07:48:44 +01:00
"Status" => 'Active',
);
}
2011-03-08 22:05:51 +01:00
< div class = "notice" markdown = '1' >
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
2011-02-07 07:48:44 +01:00
[data-types ](data-types ) for details.
2011-03-08 22:05:51 +01:00
< / div >
2011-02-07 07:48:44 +01:00
### 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").
2012-06-23 00:32:43 +02:00
Calling those functions directly will still return whatever type your PHP code generates,
2011-03-08 22:05:51 +01:00
but using the *obj()* -method or accessing through a template will cast the value according to the $casting-definition.
2011-02-07 07:48:44 +01:00
:::php
class Player extends DataObject {
2012-08-03 07:00:43 +02:00
public static $casting = array(
2011-02-07 07:48:44 +01:00
"MembershipFee" => 'Currency',
);
// $myPlayer->MembershipFee() returns a float (e.g. 123.45)
// $myPlayer->obj('MembershipFee') returns a object of type Currency
2012-06-26 17:32:46 +02:00
// In a template: < % loop MyPlayer %>MembershipFee.Nice< % end_loop %> returns a casted string (e.g. "$123.45")
2012-01-30 23:13:42 +01:00
public function getMembershipFee() {
2011-02-07 07:48:44 +01:00
return $this->Team()->BaseFee * $this->MembershipYears;
}
}
## Relations
2011-03-08 22:05:51 +01:00
Relations are built through static array definitions on a class, in the format `<relationship-name> => <classname>`
2011-02-07 07:48:44 +01:00
### has_one
A 1-to-1 relation creates a database-column called "`< relationship-name > `ID", in the example below this would be "TeamID"
on the "Player"-table.
:::php
// access with $myPlayer->Team()
class Player extends DataObject {
2012-08-03 07:00:43 +02:00
public static $has_one = array(
2011-02-07 07:48:44 +01:00
"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 {
2012-08-03 07:00:43 +02:00
public static $has_one = array(
2011-02-07 07:48:44 +01:00
"Parent" => "SiteTree",
);
}
### has_many
Defines 1-to-many joins. A database-column named ""`< relationship-name > `ID"" will to be created in the child-class.
2011-03-08 22:05:51 +01:00
< div class = "warning" markdown = '1' >
**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.
< / div >
2011-02-07 07:48:44 +01:00
:::php
// access with $myTeam->Players() or $player->Team()
class Team extends DataObject {
2012-08-03 07:00:43 +02:00
public static $has_many = array(
2011-02-07 07:48:44 +01:00
"Players" => "Player",
);
}
class Player extends DataObject {
2012-08-03 07:00:43 +02:00
public static $has_one = array(
2011-02-07 07:48:44 +01:00
"Team" => "Team",
);
}
2011-03-08 22:05:51 +01:00
To specify multiple $has_manys to the same object you can use dot notation to distinguish them like below
2011-02-07 07:48:44 +01:00
:::php
2012-08-03 07:00:43 +02:00
class Person extends DataObject {
public static $has_many = array(
2011-02-07 07:48:44 +01:00
"Managing" => "Company.Manager",
"Cleaning" => "Company.Cleaner",
);
}
2012-08-03 07:00:43 +02:00
class Company extends DataObject {
public static $has_one = array(
2011-02-07 07:48:44 +01:00
"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 {
2012-08-03 07:00:43 +02:00
public static $has_many = array(
2011-02-07 07:48:44 +01:00
"Players" => "Player",
);
}
class Player extends DataObject {
2012-08-03 07:00:43 +02:00
public static $has_one = array(
2011-02-07 07:48:44 +01:00
"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.
2011-03-08 22:05:51 +01:00
< div class = "warning" markdown = '1' >
**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.
< / div >
2011-02-07 07:48:44 +01:00
:::php
// access with $myTeam->Categories() or $myCategory->Teams()
class Team extends DataObject {
2012-08-03 07:00:43 +02:00
public static $many_many = array(
2011-02-07 07:48:44 +01:00
"Categories" => "Category",
);
}
class Category extends DataObject {
2012-08-03 07:00:43 +02:00
public static $belongs_many_many = array(
2011-02-07 07:48:44 +01:00
"Teams" => "Team",
);
}
### Adding relations
2012-08-03 07:00:43 +02:00
Adding new items to a relations works the same, regardless if you're editing a *has_many* - or a *many_many* .
They are encapsulated by `[api:HasManyList]` and `[api:ManyManyList]` , both of which provide very similar APIs,
e.g. an `add()` and `remove()` method.
2011-02-07 07:48:44 +01:00
:::php
class Team extends DataObject {
// see "many_many"-description for a sample definition of class "Category"
2012-08-03 07:00:43 +02:00
public static $many_many = array(
2011-02-07 07:48:44 +01:00
"Categories" => "Category",
);
2012-06-28 14:51:04 +02:00
public function addCategories(SS_List $cats) {
foreach($cats as $cat) $this->Categories()->add($cat);
2011-02-07 07:48:44 +01:00
}
}
2011-04-04 02:18:50 +02:00
### Custom Relations
2011-02-07 07:48:44 +01:00
2012-08-03 07:00:43 +02:00
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.
2012-06-28 14:51:04 +02:00
See `[api:DataObject::$has_many]` for more info on the described relations.
2011-02-07 07:48:44 +01:00
:::php
class Team extends DataObject {
2012-08-03 07:00:43 +02:00
public static $has_many = array(
2011-02-07 07:48:44 +01:00
"Players" => "Player"
);
2011-04-04 02:18:50 +02:00
// can be accessed by $myTeam->ActivePlayers()
2012-01-30 23:13:42 +01:00
public function ActivePlayers() {
2011-02-07 07:48:44 +01:00
return $this->Players("Status='Active'");
}
}
2012-08-03 07:00:43 +02:00
Note: Adding new records to a filtered `RelationList` like in the example above doesn't automatically set the
filtered criteria on the added record.
2012-06-28 14:51:04 +02:00
2012-06-28 11:43:30 +02:00
## Validation and Constraints
Traditionally, validation in SilverStripe has been mostly handled on the controller
through [form validation ](/topics/form-validation ).
While this is a useful approach, it can lead to data inconsistencies if the
record is modified outside of the controller and form context.
Most validation constraints are actually data constraints which belong on the model.
SilverStripe provides the `[api:DataObject->validate()]` method for this purpose.
By default, there is no validation - objects are always valid!
However, you can overload this method in your
DataObject sub-classes to specify custom validation,
or use the hook through `[api:DataExtension]` .
Invalid objects won't be able to be written - a [api:ValidationException]`
will be thrown and no write will occur.
It is expected that you call validate() in your own application to test that an object
is valid before attempting a write, and respond appropriately if it isn't.
The return value of `validate()` is a `[api:ValidationResult]` object.
You can append your own errors in there.
Example: Validate postcodes based on the selected country
:::php
class MyObject extends DataObject {
2012-08-03 07:00:43 +02:00
public static $db = array(
2012-06-28 11:43:30 +02:00
'Country' => 'Varchar',
'Postcode' => 'Varchar'
);
2012-08-03 07:00:43 +02:00
2012-06-28 11:43:30 +02:00
public function validate() {
$result = parent::validate();
if($this->Country == 'DE' & & $this->Postcode & & strlen($this->Postcode) != 5) {
$result->error('Need five digits for German postcodes');
}
return $result;
}
}
2011-10-29 03:04:17 +02:00
## 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
2012-05-09 01:29:24 +02:00
$members = Member::get()->map('ID', 'FirstName');
2011-10-29 03:04:17 +02:00
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
2012-05-09 01:29:24 +02:00
$members = Member::get()->map('ID', 'FirstName');
2011-10-29 03:04:17 +02:00
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
2012-05-09 01:29:24 +02:00
$members = Member::get();
2011-10-29 03:04:17 +02:00
$map = new SS_Map($members, 'ID', 'FirstName');
2012-06-23 00:32:43 +02:00
Note: You can also retrieve a single property from all contained records
through `[api:SS_List->column()]` .
2011-02-07 07:48:44 +01:00
## 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
2012-08-03 07:00:43 +02:00
$myPlayer = Player::get()->byID(99);
2011-02-07 07:48:44 +01:00
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
2012-08-03 07:00:43 +02:00
You can customize saving-behaviour for each DataObject, e.g. for adding workflow or data customization. The function is
triggered when calling *write()* to save the object to the database. This includes saving a page in the CMS or altering
a ModelAdmin record.
2011-02-07 07:48:44 +01:00
Example: Disallow creation of new players if the currently logged-in player is not a team-manager.
:::php
class Player extends DataObject {
2012-08-03 07:00:43 +02:00
public static $has_many = array(
2011-02-07 07:48:44 +01:00
"Teams"=>"Team"
);
2012-01-30 23:13:42 +01:00
public function onBeforeWrite() {
2011-02-07 07:48:44 +01:00
// 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();
}
2012-03-24 22:16:59 +01:00
// CAUTION: You are required to call the parent-function, otherwise SilverStripe will not execute the request.
2011-02-07 07:48:44 +01:00
parent::onBeforeWrite();
}
}
2011-03-08 22:05:51 +01:00
< div class = "notice" markdown = '1' >
Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate* . Please check for the existence of
2011-02-07 07:48:44 +01:00
$this->ID to toggle these two modes, as shown in the example above.
2011-03-08 22:05:51 +01:00
< / div >
2011-02-07 07:48:44 +01:00
### 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 {
2012-08-03 07:00:43 +02:00
public static $has_many = array(
2011-02-07 07:48:44 +01:00
"Teams"=>"Team"
);
2012-01-30 23:13:42 +01:00
public function onBeforeDelete() {
2011-02-07 07:48:44 +01:00
if(!Permission::check('PLAYER_DELETE')) {
Security::permissionFailure($this);
exit();
}
parent::onBeforeDelete();
}
}
### Saving data with forms
See [forms ](/topics/forms ).
### Saving data with custom SQL
2012-06-28 14:51:04 +02:00
See the ["sql queries" topic ](/reference/sqlquery ) for custom *INSERT* , *UPDATE* , *DELETE* queries.
2011-02-07 07:48:44 +01:00
2011-04-15 11:35:30 +02:00
## Extending DataObjects
2011-02-07 07:48:44 +01:00
2012-08-03 07:00:43 +02:00
You can add properties and methods to existing `[api:DataObjects]` s like `[api:Member]` (a core class) without
hacking core code or subclassing. See `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for
the most popular examples.
2011-02-07 07:48:44 +01:00
## FAQ
2012-08-03 07:00:43 +02:00
### What's the difference between DataObject::get() and a relation-getter?
2011-02-07 07:48:44 +01:00
2012-08-03 07:00:43 +02:00
You can work with both in pretty much the same way, but relationship-getters return a special type of collection:
2012-06-28 14:51:04 +02:00
A `[api:HasManyList]` or a `[api:ManyManyList]` with relation-specific functionality.
2011-02-07 07:48:44 +01:00
2012-06-28 14:51:04 +02:00
:::php
$myTeams = $myPlayer->Team(); // returns HasManyList
2012-08-03 07:00:43 +02:00
$myTeam->add($myOtherPlayer);