Update documentation static declarations to private

Also spelling, grammar and line length clean up.
This commit is contained in:
Will Rossiter 2013-06-08 15:14:53 +12:00
parent 31cfcdb08e
commit 6d792adab2
6 changed files with 367 additions and 238 deletions

View File

@ -1,6 +1,6 @@
# Gridfield
# GridField
Gridfield is SilverStripe's implementation of data grids. Its main purpose is to display tabular data
GridField is SilverStripe's implementation of data grids. Its main purpose is to display tabular data
in a format that is easy to view and modify. It's a can be thought of as a HTML table with some tricks.
It's built in a way that provides developers with an extensible way to display tabular data in a
@ -170,6 +170,7 @@ The namespace notation is `ManyMany[<extradata-field-name>]`, so for example
Example:
:::php
class Player extends DataObject {
private static $db = array('Name' => 'Text');
public static $many_many = array('Teams' => 'Team');

View File

@ -208,7 +208,7 @@ like this:
'Description' => 'Text'
);
public static $belongs_many_many = array(
private static $belongs_many_many = array(
'GalleryPage' => 'GalleryPage'
);
}

View File

@ -98,22 +98,25 @@ This code provides a good template:
:::php
class MyProcess extends Controller {
public static $allowed_actions = array('index');
function index() {
set_time_limit(0);
while(memory_get_usage() < 32*1024*1024) {
if($this->somethingToDo()) {
$this->doSomething();
sleep(1)
} else {
sleep(300);
}
}
}
private static $allowed_actions = array(
'index'
);
function index() {
set_time_limit(0);
while(memory_get_usage() < 32*1024*1024) {
if($this->somethingToDo()) {
$this->doSomething();
sleep(1)
} else {
sleep(300);
}
}
}
}
Step 2: Install the "daemon" command-line tool on your server.
Step 3: Use sake to start and stop your process
@ -122,8 +125,9 @@ Step 3: Use sake to start and stop your process
sake -stop MyProcess
Note that sake processes are currently a little brittle, in that the pid and log files are placed in the site root
directory, rather than somewhere sensible like /var/log or /var/run.
Note that sake processes are currently a little brittle, in that the pid and log
files are placed in the site root directory, rather than somewhere sensible like
/var/log or /var/run.
### Running Regular Tasks With Cron
@ -137,6 +141,7 @@ php /path/to/site_root/framework/cli-script.php dev/tasks/MyTask
If you find that your cron job appears to be retrieving the login screen, then you may need to use `php-cli`
instead. This is typical of a cPanel-based setup.
```
php-cli /path/to/site_root/framework/cli-script.php dev/tasks/MyTask
```

View File

@ -164,8 +164,12 @@ through `/fastfood/drivethrough/` to use the same order function.
:::php
class FastFood_Controller extends Controller {
private static $allowed_actions = array('drivethrough');
public static $url_handlers = array(
private static $allowed_actions = array(
'drivethrough'
);
private static $url_handlers = array(
'drivethrough/$Action/$ID/$Name' => 'order'
);

View File

@ -1,21 +1,26 @@
# Datamodel
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) that assumes the
following connections:
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 SilverStripe, you don't need to worry about writing SQL most of the time.
All data tables in SilverStripe are defined as subclasses of `[api:DataObject]`.
Inheritance is supported in the data model: separate tables will be linked
together, the data spread across these tables. The mapping and saving/loading
logic is handled by SilverStripe, you don't need to worry about writing SQL most
of the time.
Most of the ORM 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.
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,
and the ["sql queries" topic](/reference/sqlquery) in case you need to drop down to the bare metal.
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.
## Generating the Database Schema
@ -24,27 +29,34 @@ The SilverStripe database-schema is generated automatically by visiting the URL.
<div class="notice" markdown='1'>
Note: You need to be logged in as an administrator to perform this command,
unless your site is in "[dev mode](/topics/debugging)", or the command is run through CLI.
unless your site is in "[dev mode](/topics/debugging)", or the command is run
through CLI.
</div>
## Querying Data
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:
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:
:::php
$members = Member::get();
The ORM uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods
are `filter()` and `sort()`:
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 = Member::get()->filter(array('FirstName' => 'Sam'))->sort('Surname');
$members = Member::get()->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 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.
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
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
@ -64,10 +76,13 @@ An example of the query process in action:
echo "<p>$member->FirstName $member->Surname</p>";
}
This also means that getting the count of a list of objects will be done with a single, efficient query.
This also means that getting the count of a list of objects will be done with a
single, efficient query.
:::php
$members = Member::get()->filter(array('FirstName' => 'Sam'))->sort('Surname');
$members = Member::get()->filter(array(
'FirstName' => 'Sam'
))->sort('Surname');
// This will create an single SELECT COUNT query similar to -
// SELECT COUNT(*) FROM Members WHERE FirstName = 'Sam'
@ -76,35 +91,42 @@ This also means that getting the count of a list of objects will be done with a
### 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)`:
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 = Member::get()->byID(5);
If you have constructed a query that you know should return a single record, you can call `First()`:
If you have constructed a query that you know should return a single record, you
can call `First()`:
:::php
$member = Member::get()->filter(array('FirstName' => 'Sam', 'Surname' => 'Minnee'))->First();
$member = Member::get()->filter(array(
'FirstName' => 'Sam', 'Surname' => 'Minnee'
))->First();
### Sort
Quite often you would like to sort a list. Doing this on a list could be done in a few ways.
Quite 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 = Member::get()->sort('FirstName', 'ASC'); // ASC or DESC
$member = Member::get()->sort('FirstName'); // Ascending is implied
$members = Member::get()->sort('FirstName', 'ASC'); // ASC or DESC
$members = Member::get()->sort('FirstName'); // Ascending is implied
To reverse the sort
:::php
$member = Member::get()->sort('FirstName', 'DESC');
$members = Member::get()->sort('FirstName', 'DESC');
However you might have several entries with the same `FirstName` and would like to sort them by `FirstName`
and `LastName`
// or..
$members = Member::get()->sort('FirstName', 'ASC')->reverse();
However you might have several entries with the same `FirstName` and would like
to sort them by `FirstName` and `LastName`
:::php
$member = Member::get()->sort(array(
@ -119,18 +141,19 @@ You can also sort randomly
### 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".
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 = Member::get()->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:
In SilverStripe 2, we would have passed `"\"FirstName\" = 'Sam'` to make this
query. Now, we pass an array, `array('FirstName' => 'Sam')`, to minimize 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.
* 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.
@ -154,18 +177,19 @@ Or if you want to find both Sam and Sig.
'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.
Then there is the most complex task when you want to find Sam and Sig that has
either Age 17 or 74.
:::php
$members = Member::get()->filter(array(
'FirstName' => array('Sam', 'Sig'),
'Age' => array(17, 74)
));
// SQL: WHERE ("FirstName" IN ('Sam', 'Sig) AND "Age" IN ('17', '74))
In case you want to match multiple criteria non-exclusively (with an "OR" disjunctive),
use the `filterAny()` method instead:
In case you want to match multiple criteria non-exclusively (with an "OR"
disjunctive),use the `filterAny()` method instead:
:::php
$members = Member::get()->filterAny(array(
@ -185,11 +209,12 @@ You can also combine both conjunctive ("AND") and disjunctive ("OR") statements.
'FirstName' => 'Sam',
'Age' => 17,
));
// SQL: WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
// WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
### Exclude
The `exclude()` method is the opposite to the filter in that it removes entries from a list.
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.
@ -201,7 +226,8 @@ Remove both Sam and Sig is as easy as.
:::php
$members = Member::get()->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
As you can see it follows the same pattern as filter, so for removing only Sam
Minnée from the list:
:::php
$members = Member::get()->exclude(array(
@ -224,20 +250,24 @@ This would be equivalent to a SQL query of
### Search Filter Modifiers
The where clauses showcased in the previous two sections (filter and exclude) specify 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"`.
The where clauses showcased in the previous two sections (filter and exclude)
specify exact matches by default. However, there are a number of suffixes that
you can put on field names to change this behavior such as `":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.
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 behaviors.
These suffixes can also take modifiers themselves. The modifiers currently supported are `":not"`, `":nocase"`
and `":case"`. These negate the filter, make it case-insensitive and make it case-sensitive respectively. The
default comparison uses the database's default. For MySQL and MSSQL, this is case-insensitive. For PostgreSQL,
this is case-sensitive.
These suffixes can also take modifiers themselves. The modifiers currently
supported are `":not"`, `":nocase"` and `":case"`. These negate the filter,
make it case-insensitive and make it case-sensitive respectively. The default
comparison uses the database's default. For MySQL and MSSQL, this is
case-insensitive. For PostgreSQL, this is case-sensitive.
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.
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.
:::php
$members = Member::get()->filter(array(
@ -247,15 +277,17 @@ since 1/1/2011.
### Subtract
You can subtract entries from a DataList by passing in another DataList to `subtract()`
You can subtract entries from a DataList by passing in another DataList to
`subtract()`
:::php
$allSams = Member::get()->filter('FirstName', 'Sam');
$allMembers = Member::get();
$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.
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.
@ -263,15 +295,17 @@ use case could be when you want to find all the members that does not exist in a
### Limit
You can limit the amount of records returned in a DataList by using the `limit()` method.
You can limit the amount of records returned in a DataList by using the
`limit()` method.
:::php
// Returning the first 5 members, sorted alphabetically by Surname
$members = Member::get()->sort('Surname')->limit(5);
`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.
`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
// Return 10 members with an offset of 4 (starting from the 5th result).
@ -280,27 +314,33 @@ offset, if not provided as an argument, will default to 0.
### Raw SQL options for advanced users
Occasionally, the system described above won't let you do exactly what you need to do. In these situations, 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.
Occasionally, the system described above won't let you do exactly what you need
to do. In these situations, 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.
Under the hood, query generation is handled by the `[api:DataQuery]` class. This class does provide more direct
access to certain SQL features that `DataList` abstracts away from you.
Under the hood, query generation is handled by the `[api:DataQuery]` class. This
class does provide more direct access 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 (that documentation
still needs to be written)
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:
You can specify a WHERE clause fragment (that will be combined with other
filters using AND) with the `where()` method:
:::php
$members = Member::get()->where("\"FirstName\" = 'Sam'")
#### Joining
You can specify a join with the innerJoin and leftJoin methods. Both of these methods have the same arguments:
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
@ -310,11 +350,15 @@ For example:
:::php
// Without an alias
$members = Member::get()->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
$members = Member::get()->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "REl");
$members = Member::get()
->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
$members = Member::get()
->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
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
@ -340,9 +384,11 @@ 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>`".
"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 behavior by making a function called "get`<fieldname>`" or
"set`<fieldname>`".
:::php
class Player extends DataObject {
@ -359,17 +405,22 @@ default behaviour by making a function called "get`<fieldname>`" or "set`<fieldn
### 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.
We can create new "virtual properties" which are not actually listed in
`private 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 {
public 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
// just saves data on the object, please use $myPlayer->write() to save
// the database-row
public function setTitle($title) {
list($firstName, $surName) = explode(' ', $title);
$this->FirstName = $firstName;
@ -378,48 +429,55 @@ Here we combined a Player's first name and surname, accessible through $myPlayer
}
<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.
**CAUTION:** It is common practice to make sure that pairs of custom
getters/setter deal with the same data, in a consistent format.
</div>
<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.
**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>
### 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.
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 {
public static $defaults = array(
private static $defaults = array(
"Status" => 'Active',
);
}
<div class="notice" markdown='1'>
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
[data-types](data-types) for details.
Note: Alternatively you can set defaults directly in the database-schema (rather
than the object-model). See [data-types](data-types) for details.
</div>
### 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.
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 {
public static $casting = array(
private 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: <% loop $MyPlayer %>MembershipFee.Nice<% end_loop %> returns a casted string (e.g. "$123.45")
// In a template: <% loop $MyPlayer %>MembershipFee.Nice<% end_loop %>
// returns a casted string (e.g. "$123.45")
public function getMembershipFee() {
return $this->Team()->BaseFee * $this->MembershipYears;
}
@ -428,86 +486,98 @@ but using the *obj()*-method or accessing through a template will cast the value
## Relations
Relations are built through static array definitions on a class, in the format `<relationship-name> => <classname>`
Relations are built through static array definitions on a class, in the format
`<relationship-name> => <classname>`.
### 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.
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 {
public static $has_one = array(
private 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:
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 {
public static $has_one = array(
private static $has_one = array(
"Parent" => "SiteTree",
);
}
### has_many
Defines 1-to-many joins. A database-column named ""`<relationship-name>`ID"" will to be created in the child-class.
Defines 1-to-many joins. A database-column named ""`<relationship-name>`ID""
will to be created in the child-class.
<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.
**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>
:::php
// access with $myTeam->Players() or $player->Team()
class Team extends DataObject {
public static $has_many = array(
private static $has_many = array(
"Players" => "Player",
);
}
class Player extends DataObject {
public static $has_one = array(
private 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
To specify multiple $has_manys to the same object you can use dot notation to
distinguish them like below
:::php
class Person extends DataObject {
public static $has_many = array(
private static $has_many = array(
"Managing" => "Company.Manager",
"Cleaning" => "Company.Cleaner",
);
}
class Company extends DataObject {
public static $has_one = array(
private static $has_one = array(
"Manager" => "Person",
"Cleaner" => "Person"
);
}
Multiple $has_one relationships are okay if they aren't linking to the same object type.
Multiple $has_one relationships are okay if they aren't linking to the same
object type.
:::php
/**
* THIS IS BAD
*/
class Team extends DataObject {
public static $has_many = array(
private static $has_many = array(
"Players" => "Player",
);
}
class Player extends DataObject {
public static $has_one = array(
private static $has_one = array(
"Team" => "Team",
"AnotherTeam" => "Team",
);
@ -516,22 +586,26 @@ Multiple $has_one relationships are okay if they aren't linking to the same obje
### many_many
Defines many-to-many joins. A new table, (this-class)_(relationship-name), will be created with a pair of ID fields.
Defines many-to-many joins. A new table, (this-class)_(relationship-name), will
be created with a pair of ID fields.
<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.
**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>
:::php
// access with $myTeam->Categories() or $myCategory->Teams()
class Team extends DataObject {
public static $many_many = array(
private static $many_many = array(
"Categories" => "Category",
);
}
class Category extends DataObject {
public static $belongs_many_many = array(
private static $belongs_many_many = array(
"Teams" => "Team",
);
}
@ -539,14 +613,16 @@ accessors available on both ends.
### Adding relations
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.
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.
:::php
class Team extends DataObject {
// see "many_many"-description for a sample definition of class "Category"
public static $many_many = array(
private static $many_many = array(
"Categories" => "Category",
);
@ -558,14 +634,15 @@ e.g. an `add()` and `remove()` method.
### 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.
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 {
public static $has_many = array(
private static $has_many = array(
"Players" => "Player"
);
@ -575,39 +652,45 @@ See `[api:DataObject::$has_many]` for more info on the described relations.
}
}
Note: Adding new records to a filtered `RelationList` like in the example above doesn't automatically set the
filtered criteria on the added record.
Note: Adding new records to a filtered `RelationList` like in the example above
doesn't automatically set the filtered criteria on the added record.
### Relations on Unsaved Objects
You can also set *has_many* and *many_many* relations before the `DataObject` is saved. This behaviour uses the
`[api:UnsavedRelationList]` and converts it into the correct `RelationList` when saving the `DataObject` for the
first time.
You can also set *has_many* and *many_many* relations before the `DataObject` is
saved. This behaviour uses the `[api:UnsavedRelationList]` and converts it into
the correct `RelationList` when saving the `DataObject` for the first time.
This unsaved lists will also recursively save any unsaved objects that they contain.
This unsaved lists will also recursively save any unsaved objects that they
contain.
As these lists are not backed by the database, most of the filtering methods on `DataList` cannot be used on a
list of this type. As such, an `UnsavedRelationList` should only be used for setting a relation before saving an
object, not for displaying the objects contained in the relation.
As these lists are not backed by the database, most of the filtering methods on
`DataList` cannot be used on a list of this type. As such, an
`UnsavedRelationList` should only be used for setting a relation before saving
an object, not for displaying the objects contained in the relation.
## Validation and Constraints
Traditionally, validation in SilverStripe has been mostly handled on the controller
through [form validation](/topics/form-validation).
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.
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.
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.
@ -616,6 +699,7 @@ Example: Validate postcodes based on the selected country
:::php
class MyObject extends DataObject {
private static $db = array(
'Country' => 'Varchar',
'Postcode' => 'Varchar'
@ -632,21 +716,23 @@ Example: Validate postcodes based on the selected country
## 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:
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 = Member::get()->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:
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 = Member::get()->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`.
This functionality is provided by the `SS_Map` class, which can be used to build
a map around any `SS_List`.
:::php
$members = Member::get();
@ -657,8 +743,9 @@ through `[api:SS_List->column()]`.
## 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*.
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
@ -689,8 +776,9 @@ You have to make sure though that certain properties are not overwritten, e.g. *
);
Alternatively you can use *castedUpdate()* to respect the [data-types](/topics/data-types). This is preferred to manually
casting data before saving.
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(
@ -703,20 +791,24 @@ casting data before saving.
### onBeforeWrite
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.
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.
Example: Disallow creation of new players if the currently logged-in player is not a team-manager.
Example: Disallow creation of new players if the currently logged-in player is
not a team-manager.
:::php
class Player extends DataObject {
public static $has_many = array(
private static $has_many = array(
"Teams"=>"Team"
);
public function onBeforeWrite() {
// check on first write action, aka "database row creation" (ID-property is not set)
// check on first write action, aka "database row creation"
// (ID-property is not set)
if(!$this->ID) {
$currentPlayer = Member::currentUser();
if(!$currentPlayer->IsTeamManager()) {
@ -727,31 +819,34 @@ Example: Disallow creation of new players if the currently logged-in player is n
// check on every write action
if(!$this->record['TeamID']) {
user_error('Cannot save player without a valid team-connection', E_USER_ERROR);
user_error('Cannot save player without a valid team', E_USER_ERROR);
exit();
}
// CAUTION: You are required to call the parent-function, otherwise SilverStripe will not execute the request.
// CAUTION: You are required to call the parent-function, otherwise
// SilverStripe will not execute the request.
parent::onBeforeWrite();
}
}
<div class="notice" markdown='1'>
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.
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.
</div>
### 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".
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 {
public static $has_many = array(
private static $has_many = array(
"Teams"=>"Team"
);
@ -771,20 +866,25 @@ See [forms](/topics/forms).
### Saving data with custom SQL
See the ["sql queries" topic](/reference/sqlquery) for custom *INSERT*, *UPDATE*, *DELETE* queries.
See the ["sql queries" topic](/reference/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. See `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for
the most popular examples.
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.
## FAQ
### What's 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:HasManyList]` or a `[api:ManyManyList]` with relation-specific functionality.
You can work with both in pretty much the same way, but relationship-getters
return a special type of collection:
A `[api:HasManyList]` or a `[api:ManyManyList]` with relation-specific
functionality.
:::php
$myTeams = $myPlayer->Team(); // returns HasManyList

View File

@ -1,11 +1,13 @@
# Forms
HTML forms are in practice the most used way to communicate with a browser. SilverStripe provides classes to generate
and handle the actions and data from a form.
HTML forms are in practice the most used way to communicate with a browser.
SilverStripe provides classes to generate and handle the actions and data from a
form.
## Overview
A fully implemented form in SilverStripe includes a couple of classes that individually have separate concerns.
A fully implemented form in SilverStripe includes a couple of classes that
individually have separate concerns.
* Controller - Takes care of assembling the form and receiving data from it.
* Form - Holds sets of fields, actions and validators.
@ -13,19 +15,22 @@ A fully implemented form in SilverStripe includes a couple of classes that indiv
* FormActions - Often submit buttons that executes actions.
* Validators - Validate the whole form, see [Form validation](form-validation.md) topic for more information.
Depending on your needs you can customize and override any of the above classes, however the defaults are often
sufficient.
Depending on your needs you can customize and override any of the above classes,
however the defaults are often sufficient.
## The Controller
Forms start at the controller. Here is a simple example on how to set up a form in a controller.
Forms start at the controller. Here is a simple example on how to set up a form
in a controller.
**Page.php**
:::php
class Page_Controller extends ContentController {
public static $allowed_actions = array('HelloForm');
private static $allowed_actions = array(
'HelloForm'
);
// Template method
public function HelloForm() {
@ -45,18 +50,20 @@ Forms start at the controller. Here is a simple example on how to set up a form
}
}
The name of the form ("HelloForm") is passed into the `Form`
constructor as a second argument. It needs to match the method name.
The name of the form ("HelloForm") is passed into the `Form` constructor as a
second argument. It needs to match the method name.
Since forms need a URL, the `HelloForm()` method needs to be handled
like any other controller action. In order to whitelist its access through
URLs, we add it to the `$allowed_actions` array.
Form actions ("doSayHello") on the other hand should NOT be included here,
these are handled separately through `Form->httpSubmission()`.
You can control access on form actions either by conditionally removing
a `FormAction` from the form construction,
or by defining `$allowed_actions` in your own `Form` class
(more information in the ["controllers" topic](/topics/controllers)).
Since forms need a URL, the `HelloForm()` method needs to be handled like any
other controller action. In order to whitelist its access through URLs, we add
it to the `$allowed_actions` array.
Form actions ("doSayHello") on the other hand should NOT be included here, these
are handled separately through `Form->httpSubmission()`.
You can control access on form actions either by conditionally removing a
`FormAction` from the form construction, or by defining `$allowed_actions` in
your own `Form` class (more information in the
["controllers" topic](/topics/controllers)).
**Page.ss**
@ -65,8 +72,8 @@ or by defining `$allowed_actions` in your own `Form` class
<div>$HelloForm</div>
<div class="warning" markdown='1'>
Be sure to add the Form name 'HelloForm' to the Controller::$allowed_actions() to be sure that form submissions
get through to the correct action.
Be sure to add the Form name 'HelloForm' to the Controller::$allowed_actions()
to be sure that form submissions get through to the correct action.
</div>
<div class="notice" markdown='1'>
@ -78,13 +85,15 @@ documentation or the API documentation for `[api:Object]`::create().
## The Form
Form is the base class of all forms in a SilverStripe application. Forms in your application can be created either by
instantiating the Form class itself, or by subclassing it.
Form is the base class of all forms in a SilverStripe application. Forms in your
application can be created either by instantiating the Form class itself, or by
subclassing it.
### Instantiating a form
Creating a form is a matter of defining a method to represent that form. This method should return a form object. The
constructor takes the following arguments:
Creating a form is a matter of defining a method to represent that form. This
method should return a form object. The constructor takes the following
arguments:
* `$controller`: This must be and instance of the controller that contains the form, often `$this`.
* `$name`: This must be the name of the method on that controller that is called to return the form. The first two
@ -141,7 +150,7 @@ data.
:::php
class Page_Controller extends ContentController {
public static $allowed_actions = array(
private static $allowed_actions = array(
'HelloForm',
);
@ -161,6 +170,7 @@ data.
EmailField::create("Email"),
PasswordField::create("Password")
);
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
parent::__construct($controller, $name, $fields, $actions);
@ -180,8 +190,9 @@ There are many classes extending `[api:FormField]`. There is a full overview at
### Using Form Fields
To get these fields automatically rendered into a form element, all you need to do is create a new instance of the
class, and add it to the fieldlist of the form.
To get these fields automatically rendered into a form element, all you need to
do is create a new instance of the class, and add it to the `FieldList` of the
form.
:::php
$form = new Form(
@ -202,8 +213,9 @@ class, and add it to the fieldlist of the form.
## Readonly
You can turn a form or individual fields into a readonly version. This is handy in the case of confirmation pages or
when certain fields can be edited due to permissions.
You can turn a form or individual fields into a readonly version. This is handy
in the case of confirmation pages or when certain fields can be edited due to
permissions.
Readonly on a Form
@ -242,6 +254,7 @@ First of all, you need to create your form on it's own class, that way you can d
EmailField::create("Email"),
PasswordField::create("Password")
);
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
parent::__construct($controller, $name, $fields, $actions);
}
@ -256,11 +269,12 @@ First of all, you need to create your form on it's own class, that way you can d
}
}
`MyForm->forTemplate()` tells the `[api:Form]` class to render with a template of return value of `$this->class`, which in this case
is *MyForm*. If the template doesn't exist, then it falls back to using Form.ss.
`MyForm->forTemplate()` tells the `[api:Form]` class to render with a template
of return value of `$this->class`, which in this case is *MyForm*. If the
template doesn't exist, then it falls back to using Form.ss.
*MyForm.ss* should then be placed into your *templates/Includes* directory for your project. Here is an example of
basic customisation:
*MyForm.ss* should then be placed into your *templates/Includes* directory for
your project. Here is an example of basic customization:
:::ss
<form $FormAttributes>
@ -314,30 +328,34 @@ Will be rendered as:
:::html
<input type="text" name="MyText" class="text largeText" id="MyForm_MyCustomForm_MyText" data-validation-regex="[\d]*">
Each form field is rendered into a form via the `[FormField->FieldHolder()](api:FormField)` method, which includes
a container `<div>` as well as a `<label>` element (if applicable).
Each form field is rendered into a form via the
`[FormField->FieldHolder()](api:FormField)` method, which includes a container
`<div>` as well as a `<label>` element (if applicable).
You can also render each field without these structural elements through the `[FormField->Field()](api:FormField)`
method. In order to influence the form rendering, overloading these two methods is a good start.
You can also render each field without these structural elements through the
`[FormField->Field()](api:FormField)` method. In order to influence the form
rendering, overloading these two methods is a good start.
In addition, most form fields are rendered through SilverStripe templates, e.g. `TextareaField` is rendered via
`framework/templates/forms/TextareaField.ss`.
In addition, most form fields are rendered through SilverStripe templates, e.g.
`TextareaField` is rendered via `framework/templates/forms/TextareaField.ss`.
These templates can be overwritten globally by placing a template with the same name in your `mysite` directory,
or set on a form field instance via anyone of these methods:
These templates can be overwritten globally by placing a template with the same
name in your `mysite` directory, or set on a form field instance via anyone of
these methods:
- FormField->setTemplate()
- FormField->setFieldHolderTemplate()
- FormField->getSmallFieldHolderTemplate()
<div class="hint" markdown='1'>
Caution: Not all FormFields consistently uses templates set by the above methods.
Caution: Not all FormFields consistently uses templates set by the above methods.
</div>
### Securing forms against Cross-Site Request Forgery (CSRF)
SilverStripe tries to protect users against *Cross-Site Request Forgery (CSRF)* by adding a hidden *SecurityID*
parameter to each form. See [secure-development](/topics/security) for details.
SilverStripe tries to protect users against *Cross-Site Request Forgery (CSRF)*
by adding a hidden *SecurityID* parameter to each form. See
[secure-development](/topics/security) for details.
In addition, you should limit forms to the intended HTTP verb (mostly `GET` or `POST`)
to further reduce attack surface, by using `[api:Form->setStrictFormMethodCheck()]`.
@ -353,6 +371,7 @@ If you want to remove certain fields from your subclass:
:::php
class MyCustomForm extends MyForm {
public function __construct($controller, $name) {
parent::__construct($controller, $name);