mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Update documentation static declarations to private
Also spelling, grammar and line length clean up.
This commit is contained in:
parent
31cfcdb08e
commit
6d792adab2
@ -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.
|
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
|
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:
|
Example:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
private static $db = array('Name' => 'Text');
|
private static $db = array('Name' => 'Text');
|
||||||
public static $many_many = array('Teams' => 'Team');
|
public static $many_many = array('Teams' => 'Team');
|
||||||
|
@ -208,7 +208,7 @@ like this:
|
|||||||
'Description' => 'Text'
|
'Description' => 'Text'
|
||||||
);
|
);
|
||||||
|
|
||||||
public static $belongs_many_many = array(
|
private static $belongs_many_many = array(
|
||||||
'GalleryPage' => 'GalleryPage'
|
'GalleryPage' => 'GalleryPage'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -98,22 +98,25 @@ This code provides a good template:
|
|||||||
|
|
||||||
:::php
|
:::php
|
||||||
class MyProcess extends Controller {
|
class MyProcess extends Controller {
|
||||||
public static $allowed_actions = array('index');
|
|
||||||
function index() {
|
private static $allowed_actions = array(
|
||||||
set_time_limit(0);
|
'index'
|
||||||
while(memory_get_usage() < 32*1024*1024) {
|
);
|
||||||
if($this->somethingToDo()) {
|
|
||||||
$this->doSomething();
|
function index() {
|
||||||
sleep(1)
|
set_time_limit(0);
|
||||||
} else {
|
|
||||||
sleep(300);
|
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 2: Install the "daemon" command-line tool on your server.
|
||||||
|
|
||||||
Step 3: Use sake to start and stop your process
|
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
|
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
|
Note that sake processes are currently a little brittle, in that the pid and log
|
||||||
directory, rather than somewhere sensible like /var/log or /var/run.
|
files are placed in the site root directory, rather than somewhere sensible like
|
||||||
|
/var/log or /var/run.
|
||||||
|
|
||||||
### Running Regular Tasks With Cron
|
### 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`
|
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.
|
instead. This is typical of a cPanel-based setup.
|
||||||
|
|
||||||
```
|
```
|
||||||
php-cli /path/to/site_root/framework/cli-script.php dev/tasks/MyTask
|
php-cli /path/to/site_root/framework/cli-script.php dev/tasks/MyTask
|
||||||
```
|
```
|
||||||
|
@ -164,8 +164,12 @@ through `/fastfood/drivethrough/` to use the same order function.
|
|||||||
|
|
||||||
:::php
|
:::php
|
||||||
class FastFood_Controller extends Controller {
|
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'
|
'drivethrough/$Action/$ID/$Name' => 'order'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
# Datamodel
|
# Datamodel
|
||||||
|
|
||||||
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) that assumes the
|
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model)
|
||||||
following connections:
|
that assumes the following connections:
|
||||||
|
|
||||||
* Each database-table maps to a PHP class
|
* Each database-table maps to a PHP class
|
||||||
* Each database-row maps to a PHP object
|
* Each database-row maps to a PHP object
|
||||||
* Each database-column maps to a property on 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
|
All data tables in SilverStripe are defined as subclasses of `[api:DataObject]`.
|
||||||
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.
|
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
|
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,
|
See [database-structure](/reference/database-structure) for in-depth information
|
||||||
and the ["sql queries" topic](/reference/sqlquery) in case you need to drop down to the bare metal.
|
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
|
## Generating the Database Schema
|
||||||
|
|
||||||
@ -24,27 +29,34 @@ The SilverStripe database-schema is generated automatically by visiting the URL.
|
|||||||
|
|
||||||
<div class="notice" markdown='1'>
|
<div class="notice" markdown='1'>
|
||||||
Note: You need to be logged in as an administrator to perform this command,
|
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>
|
</div>
|
||||||
|
|
||||||
## Querying Data
|
## 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
|
:::php
|
||||||
$members = Member::get();
|
$members = Member::get();
|
||||||
|
|
||||||
The ORM uses a "fluent" syntax, where you specify a query by chaining together different methods. Two common methods
|
The ORM uses a "fluent" syntax, where you specify a query by chaining together
|
||||||
are `filter()` and `sort()`:
|
different methods. Two common methods are `filter()` and `sort()`:
|
||||||
|
|
||||||
:::php
|
:::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
|
Those of you who know a bit about SQL might be thinking "it looks like you're
|
||||||
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
|
querying all members, and then filtering to those with a first name of 'Sam'.
|
||||||
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
|
Isn't this very slow?" Is isn't, because the ORM doesn't actually execute the
|
||||||
generated by the ORM may look something like this for the previous query.
|
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
|
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>";
|
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
|
:::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 -
|
// This will create an single SELECT COUNT query similar to -
|
||||||
// SELECT COUNT(*) FROM Members WHERE FirstName = 'Sam'
|
// 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
|
### 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,
|
There are a couple of ways of getting a single DataObject from the ORM. If you
|
||||||
you can use `byID($id)`:
|
know the ID number of the object, you can use `byID($id)`:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$member = Member::get()->byID(5);
|
$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
|
:::php
|
||||||
$member = Member::get()->filter(array('FirstName' => 'Sam', 'Surname' => 'Minnee'))->First();
|
$member = Member::get()->filter(array(
|
||||||
|
'FirstName' => 'Sam', 'Surname' => 'Minnee'
|
||||||
|
))->First();
|
||||||
|
|
||||||
|
|
||||||
### Sort
|
### 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).
|
If would like to sort the list by `FirstName` in a ascending way (from A to Z).
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$member = Member::get()->sort('FirstName', 'ASC'); // ASC or DESC
|
$members = Member::get()->sort('FirstName', 'ASC'); // ASC or DESC
|
||||||
$member = Member::get()->sort('FirstName'); // Ascending is implied
|
$members = Member::get()->sort('FirstName'); // Ascending is implied
|
||||||
|
|
||||||
To reverse the sort
|
To reverse the sort
|
||||||
|
|
||||||
:::php
|
:::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`
|
// or..
|
||||||
and `LastName`
|
$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
|
:::php
|
||||||
$member = Member::get()->sort(array(
|
$member = Member::get()->sort(array(
|
||||||
@ -119,18 +141,19 @@ You can also sort randomly
|
|||||||
|
|
||||||
### Filter
|
### Filter
|
||||||
|
|
||||||
As you might expect, the `filter()` method filters the list of objects that gets returned. The previous example
|
As you might expect, the `filter()` method filters the list of objects that gets
|
||||||
included this filter, which returns all Members with a first name of "Sam".
|
returned. The previous example included this filter, which returns all Members
|
||||||
|
with a first name of "Sam".
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$members = Member::get()->filter(array('FirstName' => 'Sam'));
|
$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,
|
In SilverStripe 2, we would have passed `"\"FirstName\" = 'Sam'` to make this
|
||||||
`array('FirstName' => 'Sam')`, to minimise the risk of SQL injection bugs. The format of this array follows a few
|
query. Now, we pass an array, `array('FirstName' => 'Sam')`, to minimize the
|
||||||
rules:
|
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
|
* Each element of the array specifies a filter. You can specify as many
|
||||||
be true.
|
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 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.
|
* 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')
|
'FirstName', array('Sam', 'Sig')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Then there is the most complex task when you want to find Sam and Sig that has
|
||||||
Then there is the most complex task when you want to find Sam and Sig that has either Age 17 or 74.
|
either Age 17 or 74.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$members = Member::get()->filter(array(
|
$members = Member::get()->filter(array(
|
||||||
'FirstName' => array('Sam', 'Sig'),
|
'FirstName' => array('Sam', 'Sig'),
|
||||||
'Age' => array(17, 74)
|
'Age' => array(17, 74)
|
||||||
));
|
));
|
||||||
|
|
||||||
// SQL: WHERE ("FirstName" IN ('Sam', 'Sig) AND "Age" IN ('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),
|
In case you want to match multiple criteria non-exclusively (with an "OR"
|
||||||
use the `filterAny()` method instead:
|
disjunctive),use the `filterAny()` method instead:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$members = Member::get()->filterAny(array(
|
$members = Member::get()->filterAny(array(
|
||||||
@ -185,11 +209,12 @@ You can also combine both conjunctive ("AND") and disjunctive ("OR") statements.
|
|||||||
'FirstName' => 'Sam',
|
'FirstName' => 'Sam',
|
||||||
'Age' => 17,
|
'Age' => 17,
|
||||||
));
|
));
|
||||||
// SQL: WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
|
// WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
|
||||||
|
|
||||||
### Exclude
|
### 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.
|
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
|
:::php
|
||||||
$members = Member::get()->exclude('FirstName', array('Sam','Sig'));
|
$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
|
:::php
|
||||||
$members = Member::get()->exclude(array(
|
$members = Member::get()->exclude(array(
|
||||||
@ -224,20 +250,24 @@ This would be equivalent to a SQL query of
|
|||||||
|
|
||||||
### Search Filter Modifiers
|
### Search Filter Modifiers
|
||||||
|
|
||||||
The where clauses showcased in the previous two sections (filter and exclude) specify exact
|
The where clauses showcased in the previous two sections (filter and exclude)
|
||||||
matches by default. However, there are a number of suffixes that you can put on field names to change this
|
specify exact matches by default. However, there are a number of suffixes that
|
||||||
behaviour `":StartsWith"`, `":EndsWith"`, `":PartialMatch"`, `":GreaterThan"`, `":LessThan"`, `":Negation"`.
|
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
|
Each of these suffixes is represented in the ORM as a subclass of
|
||||||
their own SearchFilters if needing to extend the ORM filter and exclude behaviours.
|
`[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"`
|
These suffixes can also take modifiers themselves. The modifiers currently
|
||||||
and `":case"`. These negate the filter, make it case-insensitive and make it case-sensitive respectively. The
|
supported are `":not"`, `":nocase"` and `":case"`. These negate the filter,
|
||||||
default comparison uses the database's default. For MySQL and MSSQL, this is case-insensitive. For PostgreSQL,
|
make it case-insensitive and make it case-sensitive respectively. The default
|
||||||
this is case-sensitive.
|
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
|
The following is a query which will return everyone whose first name doesn't
|
||||||
since 1/1/2011.
|
start with S, who has logged in since 1/1/2011.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$members = Member::get()->filter(array(
|
$members = Member::get()->filter(array(
|
||||||
@ -247,15 +277,17 @@ since 1/1/2011.
|
|||||||
|
|
||||||
### Subtract
|
### 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
|
:::php
|
||||||
$allSams = Member::get()->filter('FirstName', 'Sam');
|
$allSams = Member::get()->filter('FirstName', 'Sam');
|
||||||
$allMembers = Member::get();
|
$allMembers = Member::get();
|
||||||
$noSams = $allMembers->subtract($allSams);
|
$noSams = $allMembers->subtract($allSams);
|
||||||
|
|
||||||
Though for the above example it would probably be easier to use `filter()` and `exclude()`. A better
|
Though for the above example it would probably be easier to use `filter()` and
|
||||||
use case could be when you want to find all the members that does not exist in a Group.
|
`exclude()`. A better use case could be when you want to find all the members
|
||||||
|
that does not exist in a Group.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
// ... Finding all members that does not belong to $group.
|
// ... 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
|
### 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
|
:::php
|
||||||
// Returning the first 5 members, sorted alphabetically by Surname
|
// Returning the first 5 members, sorted alphabetically by Surname
|
||||||
$members = Member::get()->sort('Surname')->limit(5);
|
$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
|
`limit()` accepts two arguments, the first being the amount of results you want
|
||||||
parameter to specify the offset, which allows you to tell the system where to start getting the results from. The
|
returned, with an optional second parameter to specify the offset, which allows
|
||||||
offset, if not provided as an argument, will default to 0.
|
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
|
:::php
|
||||||
// Return 10 members with an offset of 4 (starting from the 5th result).
|
// 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
|
### 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
|
Occasionally, the system described above won't let you do exactly what you need
|
||||||
methods that manipulate the SQL query at a lower level. When using these, please ensure that all table & field names
|
to do. In these situations, we have methods that manipulate the SQL query at a
|
||||||
are escaped with double quotes, otherwise some DB back-ends (e.g. PostgreSQL) won't work.
|
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
|
Under the hood, query generation is handled by the `[api:DataQuery]` class. This
|
||||||
access to certain SQL features that `DataList` abstracts away from you.
|
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
|
In general, we advise against using these methods unless it's absolutely
|
||||||
you need it to, you may also consider extending the ORM with new data types or filter modifiers (that documentation
|
necessary. If the ORM doesn't do quite what you need it to, you may also
|
||||||
still needs to be written)
|
consider extending the ORM with new data types or filter modifiers (that
|
||||||
|
documentation still needs to be written)
|
||||||
|
|
||||||
#### Where clauses
|
#### 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
|
:::php
|
||||||
$members = Member::get()->where("\"FirstName\" = 'Sam'")
|
$members = Member::get()->where("\"FirstName\" = 'Sam'")
|
||||||
|
|
||||||
#### Joining
|
#### 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 name of the table to join to
|
||||||
* The filter clause for the join
|
* The filter clause for the join
|
||||||
@ -310,11 +350,15 @@ For example:
|
|||||||
|
|
||||||
:::php
|
:::php
|
||||||
// Without an alias
|
// Without an alias
|
||||||
$members = Member::get()->leftJoin("Group_Members", "\"Group_Members\".\"MemberID\" = \"Member\".\"ID\"");
|
$members = Member::get()
|
||||||
$members = Member::get()->innerJoin("Group_Members", "\"Rel\".\"MemberID\" = \"Member\".\"ID\"", "REl");
|
->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
|
Passing a *$join* statement to DataObject::get will filter results further by
|
||||||
table. **It will NOT return the additionally joined data.** The returned *$records* will always be a
|
the JOINs performed against the foreign table. **It will NOT return the
|
||||||
|
additionally joined data.** The returned *$records* will always be a
|
||||||
`[api:DataObject]`.
|
`[api:DataObject]`.
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
@ -340,9 +384,11 @@ See [data-types](data-types) for all available types.
|
|||||||
|
|
||||||
### Overloading
|
### Overloading
|
||||||
|
|
||||||
"Getters" and "Setters" are functions that help us save fields to our data objects. By default, the methods getField()
|
"Getters" and "Setters" are functions that help us save fields to our data
|
||||||
and setField() are used to set data object fields. They save to the protected array, $obj->record. We can overload the
|
objects. By default, the methods getField() and setField() are used to set data
|
||||||
default behaviour by making a function called "get`<fieldname>`" or "set`<fieldname>`".
|
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
|
:::php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
@ -359,17 +405,22 @@ default behaviour by making a function called "get`<fieldname>`" or "set`<fieldn
|
|||||||
|
|
||||||
### Customizing
|
### Customizing
|
||||||
|
|
||||||
We can create new "virtual properties" which are not actually listed in *static $db* or stored in the database-row.
|
We can create new "virtual properties" which are not actually listed in
|
||||||
Here we combined a Player's first name and surname, accessible through $myPlayer->Title.
|
`private static $db` or stored in the database-row.
|
||||||
|
|
||||||
|
Here we combined a Player's first name and surname, accessible through
|
||||||
|
$myPlayer->Title.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
|
|
||||||
public function getTitle() {
|
public function getTitle() {
|
||||||
return "{$this->FirstName} {$this->Surname}";
|
return "{$this->FirstName} {$this->Surname}";
|
||||||
}
|
}
|
||||||
|
|
||||||
// access through $myPlayer->Title = "John Doe";
|
// 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) {
|
public function setTitle($title) {
|
||||||
list($firstName, $surName) = explode(' ', $title);
|
list($firstName, $surName) = explode(' ', $title);
|
||||||
$this->FirstName = $firstName;
|
$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'>
|
<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
|
**CAUTION:** It is common practice to make sure that pairs of custom
|
||||||
format.
|
getters/setter deal with the same data, in a consistent format.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
<div class="warning" markdown='1'>
|
||||||
**CAUTION:** Custom setters can be hard to debug: Please double check if you could transform your data in more
|
**CAUTION:** Custom setters can be hard to debug: Please double check if you
|
||||||
straight-forward logic embedded to your custom controller or form-saving.
|
could transform your data in more straight-forward logic embedded to your custom
|
||||||
|
controller or form-saving.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Default Values
|
### Default Values
|
||||||
|
|
||||||
Define the default values for all the $db fields. This example sets the "Status"-column on Player to "Active" whenever a
|
Define the default values for all the $db fields. This example sets the
|
||||||
new object is created.
|
"Status"-column on Player to "Active" whenever a new object is created.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
public static $defaults = array(
|
|
||||||
|
private static $defaults = array(
|
||||||
"Status" => 'Active',
|
"Status" => 'Active',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
<div class="notice" markdown='1'>
|
||||||
Note: Alternatively you can set defaults directly in the database-schema (rather than the object-model). See
|
Note: Alternatively you can set defaults directly in the database-schema (rather
|
||||||
[data-types](data-types) for details.
|
than the object-model). See [data-types](data-types) for details.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Casting
|
### Casting
|
||||||
|
|
||||||
Properties defined in *static $db* are automatically casted to their [data-types](data-types) when used in templates.
|
Properties defined in *static $db* are automatically casted to their
|
||||||
You can also cast the return-values of your custom functions (e.g. your "virtual properties").
|
[data-types](data-types) when used in templates.
|
||||||
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.
|
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
|
:::php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
public static $casting = array(
|
|
||||||
|
private static $casting = array(
|
||||||
"MembershipFee" => 'Currency',
|
"MembershipFee" => 'Currency',
|
||||||
);
|
);
|
||||||
|
|
||||||
// $myPlayer->MembershipFee() returns a float (e.g. 123.45)
|
// $myPlayer->MembershipFee() returns a float (e.g. 123.45)
|
||||||
// $myPlayer->obj('MembershipFee') returns a object of type Currency
|
// $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() {
|
public function getMembershipFee() {
|
||||||
return $this->Team()->BaseFee * $this->MembershipYears;
|
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
|
||||||
|
|
||||||
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
|
### has_one
|
||||||
|
|
||||||
A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in the example below this would be "TeamID"
|
A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in
|
||||||
on the "Player"-table.
|
the example below this would be "TeamID" on the "Player"-table.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
// access with $myPlayer->Team()
|
// access with $myPlayer->Team()
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
public static $has_one = array(
|
|
||||||
|
private static $has_one = array(
|
||||||
"Team" => "Team",
|
"Team" => "Team",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SilverStripe's `[api:SiteTree]` base-class for content-pages uses a 1-to-1 relationship to link to its
|
SilverStripe's `[api:SiteTree]` base-class for content-pages uses a 1-to-1
|
||||||
parent element in the tree:
|
relationship to link to its parent element in the tree:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
// access with $mySiteTree->Parent()
|
// access with $mySiteTree->Parent()
|
||||||
class SiteTree extends DataObject {
|
class SiteTree extends DataObject {
|
||||||
public static $has_one = array(
|
private static $has_one = array(
|
||||||
"Parent" => "SiteTree",
|
"Parent" => "SiteTree",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
### has_many
|
### 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'>
|
<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
|
**CAUTION:** Please specify a $has_one-relationship on the related child-class
|
||||||
accessors available on both ends.
|
as well, in order to have the necessary accessors available on both ends.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
// access with $myTeam->Players() or $player->Team()
|
// access with $myTeam->Players() or $player->Team()
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
public static $has_many = array(
|
|
||||||
|
private static $has_many = array(
|
||||||
"Players" => "Player",
|
"Players" => "Player",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
public static $has_one = array(
|
|
||||||
|
private static $has_one = array(
|
||||||
"Team" => "Team",
|
"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
|
:::php
|
||||||
class Person extends DataObject {
|
class Person extends DataObject {
|
||||||
public static $has_many = array(
|
|
||||||
|
private static $has_many = array(
|
||||||
"Managing" => "Company.Manager",
|
"Managing" => "Company.Manager",
|
||||||
"Cleaning" => "Company.Cleaner",
|
"Cleaning" => "Company.Cleaner",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Company extends DataObject {
|
class Company extends DataObject {
|
||||||
public static $has_one = array(
|
|
||||||
|
private static $has_one = array(
|
||||||
"Manager" => "Person",
|
"Manager" => "Person",
|
||||||
"Cleaner" => "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
|
:::php
|
||||||
/**
|
/**
|
||||||
* THIS IS BAD
|
* THIS IS BAD
|
||||||
*/
|
*/
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
public static $has_many = array(
|
|
||||||
|
private static $has_many = array(
|
||||||
"Players" => "Player",
|
"Players" => "Player",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
public static $has_one = array(
|
private static $has_one = array(
|
||||||
"Team" => "Team",
|
"Team" => "Team",
|
||||||
"AnotherTeam" => "Team",
|
"AnotherTeam" => "Team",
|
||||||
);
|
);
|
||||||
@ -516,22 +586,26 @@ Multiple $has_one relationships are okay if they aren't linking to the same obje
|
|||||||
|
|
||||||
### many_many
|
### 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'>
|
<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
|
**CAUTION:** Please specify a $belongs_many_many-relationship on the related
|
||||||
accessors available on both ends.
|
class as well, in order to have the necessary accessors available on both ends.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
// access with $myTeam->Categories() or $myCategory->Teams()
|
// access with $myTeam->Categories() or $myCategory->Teams()
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
public static $many_many = array(
|
|
||||||
|
private static $many_many = array(
|
||||||
"Categories" => "Category",
|
"Categories" => "Category",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Category extends DataObject {
|
class Category extends DataObject {
|
||||||
public static $belongs_many_many = array(
|
|
||||||
|
private static $belongs_many_many = array(
|
||||||
"Teams" => "Team",
|
"Teams" => "Team",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -539,14 +613,16 @@ accessors available on both ends.
|
|||||||
|
|
||||||
### Adding relations
|
### Adding relations
|
||||||
|
|
||||||
Adding new items to a relations works the same, regardless if you're editing a *has_many*- or a *many_many*.
|
Adding new items to a relations works the same, regardless if you're editing a
|
||||||
They are encapsulated by `[api:HasManyList]` and `[api:ManyManyList]`, both of which provide very similar APIs,
|
*has_many*- or a *many_many*. They are encapsulated by `[api:HasManyList]` and
|
||||||
e.g. an `add()` and `remove()` method.
|
`[api:ManyManyList]`, both of which provide very similar APIs, e.g. an `add()`
|
||||||
|
and `remove()` method.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
|
|
||||||
// see "many_many"-description for a sample definition of class "Category"
|
// see "many_many"-description for a sample definition of class "Category"
|
||||||
public static $many_many = array(
|
private static $many_many = array(
|
||||||
"Categories" => "Category",
|
"Categories" => "Category",
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -558,14 +634,15 @@ e.g. an `add()` and `remove()` method.
|
|||||||
|
|
||||||
### Custom Relations
|
### Custom Relations
|
||||||
|
|
||||||
You can use the flexible datamodel to get a filtered result-list without writing any SQL. For example, this snippet
|
You can use the flexible datamodel to get a filtered result-list without writing
|
||||||
gets you the "Players"-relation on a team, but only containing active players.
|
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.
|
See `[api:DataObject::$has_many]` for more info on the described relations.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class Team extends DataObject {
|
class Team extends DataObject {
|
||||||
public static $has_many = array(
|
private static $has_many = array(
|
||||||
"Players" => "Player"
|
"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
|
Note: Adding new records to a filtered `RelationList` like in the example above
|
||||||
filtered criteria on the added record.
|
doesn't automatically set the filtered criteria on the added record.
|
||||||
|
|
||||||
### Relations on Unsaved Objects
|
### Relations on Unsaved Objects
|
||||||
|
|
||||||
You can also set *has_many* and *many_many* relations before the `DataObject` is saved. This behaviour uses the
|
You can also set *has_many* and *many_many* relations before the `DataObject` is
|
||||||
`[api:UnsavedRelationList]` and converts it into the correct `RelationList` when saving the `DataObject` for the
|
saved. This behaviour uses the `[api:UnsavedRelationList]` and converts it into
|
||||||
first time.
|
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
|
As these lists are not backed by the database, most of the filtering methods on
|
||||||
list of this type. As such, an `UnsavedRelationList` should only be used for setting a relation before saving an
|
`DataList` cannot be used on a list of this type. As such, an
|
||||||
object, not for displaying the objects contained in the relation.
|
`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
|
## Validation and Constraints
|
||||||
|
|
||||||
Traditionally, validation in SilverStripe has been mostly handled on the controller
|
Traditionally, validation in SilverStripe has been mostly handled on the
|
||||||
through [form validation](/topics/form-validation).
|
controller through [form validation](/topics/form-validation).
|
||||||
|
|
||||||
While this is a useful approach, it can lead to data inconsistencies if the
|
While this is a useful approach, it can lead to data inconsistencies if the
|
||||||
record is modified outside of the controller and form context.
|
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!
|
By default, there is no validation - objects are always valid!
|
||||||
However, you can overload this method in your
|
However, you can overload this method in your
|
||||||
DataObject sub-classes to specify custom validation,
|
DataObject sub-classes to specify custom validation,
|
||||||
or use the hook through `[api:DataExtension]`.
|
or use the hook through `[api:DataExtension]`.
|
||||||
|
|
||||||
Invalid objects won't be able to be written - a [api:ValidationException]`
|
Invalid objects won't be able to be written - a [api:ValidationException]` will
|
||||||
will be thrown and no write will occur.
|
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.
|
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.
|
The return value of `validate()` is a `[api:ValidationResult]` object.
|
||||||
You can append your own errors in there.
|
You can append your own errors in there.
|
||||||
@ -616,6 +699,7 @@ Example: Validate postcodes based on the selected country
|
|||||||
|
|
||||||
:::php
|
:::php
|
||||||
class MyObject extends DataObject {
|
class MyObject extends DataObject {
|
||||||
|
|
||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Country' => 'Varchar',
|
'Country' => 'Varchar',
|
||||||
'Postcode' => 'Varchar'
|
'Postcode' => 'Varchar'
|
||||||
@ -632,21 +716,23 @@ Example: Validate postcodes based on the selected country
|
|||||||
|
|
||||||
## Maps
|
## Maps
|
||||||
|
|
||||||
A map is an array where the array indexes contain data as well as the values. You can build a map
|
A map is an array where the array indexes contain data as well as the values.
|
||||||
from any DataList like this:
|
You can build a map from any DataList like this:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$members = Member::get()->map('ID', 'FirstName');
|
$members = Member::get()->map('ID', 'FirstName');
|
||||||
|
|
||||||
This will return a map where the keys are Member IDs, and the values are the corresponding FirstName
|
This will return a map where the keys are Member IDs, and the values are the
|
||||||
values. Like everything else in the ORM, these maps are lazy loaded, so the following code will only
|
corresponding FirstName values. Like everything else in the ORM, these maps are
|
||||||
query a single record from the database:
|
lazy loaded, so the following code will only query a single record from the
|
||||||
|
database:
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$members = Member::get()->map('ID', 'FirstName');
|
$members = Member::get()->map('ID', 'FirstName');
|
||||||
echo $member[5];
|
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
|
:::php
|
||||||
$members = Member::get();
|
$members = Member::get();
|
||||||
@ -657,8 +743,9 @@ through `[api:SS_List->column()]`.
|
|||||||
|
|
||||||
## Data Handling
|
## Data Handling
|
||||||
|
|
||||||
When saving data through the object model, you don't have to manually escape strings to create SQL-safe commands.
|
When saving data through the object model, you don't have to manually escape
|
||||||
You have to make sure though that certain properties are not overwritten, e.g. *ID* or *ClassName*.
|
strings to create SQL-safe commands. You have to make sure though that certain
|
||||||
|
properties are not overwritten, e.g. *ID* or *ClassName*.
|
||||||
|
|
||||||
### Creation
|
### 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
|
Alternatively you can use *castedUpdate()* to respect the
|
||||||
casting data before saving.
|
[data-types](/topics/data-types). This is preferred to manually casting data
|
||||||
|
before saving.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$myPlayer->castedUpdate(
|
$myPlayer->castedUpdate(
|
||||||
@ -703,20 +791,24 @@ casting data before saving.
|
|||||||
|
|
||||||
### onBeforeWrite
|
### onBeforeWrite
|
||||||
|
|
||||||
You can customize saving-behaviour for each DataObject, e.g. for adding workflow or data customization. The function is
|
You can customize saving-behaviour for each DataObject, e.g. for adding workflow
|
||||||
triggered when calling *write()* to save the object to the database. This includes saving a page in the CMS or altering
|
or data customization. The function is triggered when calling *write()* to save
|
||||||
a ModelAdmin record.
|
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
|
:::php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
public static $has_many = array(
|
|
||||||
|
private static $has_many = array(
|
||||||
"Teams"=>"Team"
|
"Teams"=>"Team"
|
||||||
);
|
);
|
||||||
|
|
||||||
public function onBeforeWrite() {
|
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) {
|
if(!$this->ID) {
|
||||||
$currentPlayer = Member::currentUser();
|
$currentPlayer = Member::currentUser();
|
||||||
if(!$currentPlayer->IsTeamManager()) {
|
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
|
// check on every write action
|
||||||
if(!$this->record['TeamID']) {
|
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();
|
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();
|
parent::onBeforeWrite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
<div class="notice" markdown='1'>
|
||||||
Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*. Please check for the existence of
|
Note: There are no separate methods for *onBeforeCreate* and *onBeforeUpdate*.
|
||||||
$this->ID to toggle these two modes, as shown in the example above.
|
Please check for the existence of $this->ID to toggle these two modes, as shown
|
||||||
|
in the example above.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### onBeforeDelete
|
### onBeforeDelete
|
||||||
|
|
||||||
Triggered before executing *delete()* on an existing object.
|
Triggered before executing *delete()* on an existing object.
|
||||||
|
|
||||||
Example: Checking for a specific [permission](/reference/permission) to delete this type of object.
|
Example: Checking for a specific [permission](/reference/permission) to delete
|
||||||
It checks if a member is logged in who belongs to a group containing the permission "PLAYER_DELETE".
|
this type of object. It checks if a member is logged in who belongs to a group
|
||||||
|
containing the permission "PLAYER_DELETE".
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class Player extends DataObject {
|
class Player extends DataObject {
|
||||||
public static $has_many = array(
|
private static $has_many = array(
|
||||||
"Teams"=>"Team"
|
"Teams"=>"Team"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -771,20 +866,25 @@ See [forms](/topics/forms).
|
|||||||
|
|
||||||
### Saving data with custom SQL
|
### 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
|
## Extending DataObjects
|
||||||
|
|
||||||
You can add properties and methods to existing `[api:DataObjects]`s like `[api:Member]` (a core class) without
|
You can add properties and methods to existing `[api:DataObjects]`s like
|
||||||
hacking core code or subclassing. See `[api:DataExtension]` for a general description, and `[api:Hierarchy]` for
|
`[api:Member]` (a core class) without hacking core code or subclassing. See
|
||||||
the most popular examples.
|
`[api:DataExtension]` for a general description, and `[api:Hierarchy]` for the
|
||||||
|
most popular examples.
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### What's the difference between DataObject::get() and a relation-getter?
|
### 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:
|
You can work with both in pretty much the same way, but relationship-getters
|
||||||
A `[api:HasManyList]` or a `[api:ManyManyList]` with relation-specific functionality.
|
return a special type of collection:
|
||||||
|
|
||||||
|
A `[api:HasManyList]` or a `[api:ManyManyList]` with relation-specific
|
||||||
|
functionality.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$myTeams = $myPlayer->Team(); // returns HasManyList
|
$myTeams = $myPlayer->Team(); // returns HasManyList
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
# Forms
|
# Forms
|
||||||
|
|
||||||
HTML forms are in practice the most used way to communicate with a browser. SilverStripe provides classes to generate
|
HTML forms are in practice the most used way to communicate with a browser.
|
||||||
and handle the actions and data from a form.
|
SilverStripe provides classes to generate and handle the actions and data from a
|
||||||
|
form.
|
||||||
|
|
||||||
## Overview
|
## 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.
|
* Controller - Takes care of assembling the form and receiving data from it.
|
||||||
* Form - Holds sets of fields, actions and validators.
|
* 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.
|
* FormActions - Often submit buttons that executes actions.
|
||||||
* Validators - Validate the whole form, see [Form validation](form-validation.md) topic for more information.
|
* 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
|
Depending on your needs you can customize and override any of the above classes,
|
||||||
sufficient.
|
however the defaults are often sufficient.
|
||||||
|
|
||||||
## The Controller
|
## 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**
|
**Page.php**
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
public static $allowed_actions = array('HelloForm');
|
private static $allowed_actions = array(
|
||||||
|
'HelloForm'
|
||||||
|
);
|
||||||
|
|
||||||
// Template method
|
// Template method
|
||||||
public function HelloForm() {
|
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`
|
The name of the form ("HelloForm") is passed into the `Form` constructor as a
|
||||||
constructor as a second argument. It needs to match the method name.
|
second argument. It needs to match the method name.
|
||||||
|
|
||||||
Since forms need a URL, the `HelloForm()` method needs to be handled
|
Since forms need a URL, the `HelloForm()` method needs to be handled like any
|
||||||
like any other controller action. In order to whitelist its access through
|
other controller action. In order to whitelist its access through URLs, we add
|
||||||
URLs, we add it to the `$allowed_actions` array.
|
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()`.
|
Form actions ("doSayHello") on the other hand should NOT be included here, these
|
||||||
You can control access on form actions either by conditionally removing
|
are handled separately through `Form->httpSubmission()`.
|
||||||
a `FormAction` from the form construction,
|
|
||||||
or by defining `$allowed_actions` in your own `Form` class
|
You can control access on form actions either by conditionally removing a
|
||||||
(more information in the ["controllers" topic](/topics/controllers)).
|
`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**
|
**Page.ss**
|
||||||
|
|
||||||
@ -65,8 +72,8 @@ or by defining `$allowed_actions` in your own `Form` class
|
|||||||
<div>$HelloForm</div>
|
<div>$HelloForm</div>
|
||||||
|
|
||||||
<div class="warning" markdown='1'>
|
<div class="warning" markdown='1'>
|
||||||
Be sure to add the Form name 'HelloForm' to the Controller::$allowed_actions() to be sure that form submissions
|
Be sure to add the Form name 'HelloForm' to the Controller::$allowed_actions()
|
||||||
get through to the correct action.
|
to be sure that form submissions get through to the correct action.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="notice" markdown='1'>
|
<div class="notice" markdown='1'>
|
||||||
@ -78,13 +85,15 @@ documentation or the API documentation for `[api:Object]`::create().
|
|||||||
|
|
||||||
## The Form
|
## The Form
|
||||||
|
|
||||||
Form is the base class of all forms in a SilverStripe application. Forms in your application can be created either by
|
Form is the base class of all forms in a SilverStripe application. Forms in your
|
||||||
instantiating the Form class itself, or by subclassing it.
|
application can be created either by instantiating the Form class itself, or by
|
||||||
|
subclassing it.
|
||||||
|
|
||||||
### Instantiating a form
|
### 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
|
Creating a form is a matter of defining a method to represent that form. This
|
||||||
constructor takes the following arguments:
|
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`.
|
* `$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
|
* `$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
|
:::php
|
||||||
class Page_Controller extends ContentController {
|
class Page_Controller extends ContentController {
|
||||||
|
|
||||||
public static $allowed_actions = array(
|
private static $allowed_actions = array(
|
||||||
'HelloForm',
|
'HelloForm',
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -161,6 +170,7 @@ data.
|
|||||||
EmailField::create("Email"),
|
EmailField::create("Email"),
|
||||||
PasswordField::create("Password")
|
PasswordField::create("Password")
|
||||||
);
|
);
|
||||||
|
|
||||||
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
||||||
|
|
||||||
parent::__construct($controller, $name, $fields, $actions);
|
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
|
### 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
|
To get these fields automatically rendered into a form element, all you need to
|
||||||
class, and add it to the fieldlist of the form.
|
do is create a new instance of the class, and add it to the `FieldList` of the
|
||||||
|
form.
|
||||||
|
|
||||||
:::php
|
:::php
|
||||||
$form = new Form(
|
$form = new Form(
|
||||||
@ -202,8 +213,9 @@ class, and add it to the fieldlist of the form.
|
|||||||
|
|
||||||
## Readonly
|
## Readonly
|
||||||
|
|
||||||
You can turn a form or individual fields into a readonly version. This is handy in the case of confirmation pages or
|
You can turn a form or individual fields into a readonly version. This is handy
|
||||||
when certain fields can be edited due to permissions.
|
in the case of confirmation pages or when certain fields can be edited due to
|
||||||
|
permissions.
|
||||||
|
|
||||||
Readonly on a Form
|
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"),
|
EmailField::create("Email"),
|
||||||
PasswordField::create("Password")
|
PasswordField::create("Password")
|
||||||
);
|
);
|
||||||
|
|
||||||
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
$actions = new FieldList(FormAction::create("login")->setTitle("Log in"));
|
||||||
parent::__construct($controller, $name, $fields, $actions);
|
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
|
`MyForm->forTemplate()` tells the `[api:Form]` class to render with a template
|
||||||
is *MyForm*. If the template doesn't exist, then it falls back to using Form.ss.
|
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
|
*MyForm.ss* should then be placed into your *templates/Includes* directory for
|
||||||
basic customisation:
|
your project. Here is an example of basic customization:
|
||||||
|
|
||||||
:::ss
|
:::ss
|
||||||
<form $FormAttributes>
|
<form $FormAttributes>
|
||||||
@ -314,30 +328,34 @@ Will be rendered as:
|
|||||||
:::html
|
:::html
|
||||||
<input type="text" name="MyText" class="text largeText" id="MyForm_MyCustomForm_MyText" data-validation-regex="[\d]*">
|
<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
|
Each form field is rendered into a form via the
|
||||||
a container `<div>` as well as a `<label>` element (if applicable).
|
`[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)`
|
You can also render each field without these structural elements through the
|
||||||
method. In order to influence the form rendering, overloading these two methods is a good start.
|
`[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
|
In addition, most form fields are rendered through SilverStripe templates, e.g.
|
||||||
`framework/templates/forms/TextareaField.ss`.
|
`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,
|
These templates can be overwritten globally by placing a template with the same
|
||||||
or set on a form field instance via anyone of these methods:
|
name in your `mysite` directory, or set on a form field instance via anyone of
|
||||||
|
these methods:
|
||||||
|
|
||||||
- FormField->setTemplate()
|
- FormField->setTemplate()
|
||||||
- FormField->setFieldHolderTemplate()
|
- FormField->setFieldHolderTemplate()
|
||||||
- FormField->getSmallFieldHolderTemplate()
|
- FormField->getSmallFieldHolderTemplate()
|
||||||
|
|
||||||
<div class="hint" markdown='1'>
|
<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>
|
</div>
|
||||||
|
|
||||||
### Securing forms against Cross-Site Request Forgery (CSRF)
|
### Securing forms against Cross-Site Request Forgery (CSRF)
|
||||||
|
|
||||||
SilverStripe tries to protect users against *Cross-Site Request Forgery (CSRF)* by adding a hidden *SecurityID*
|
SilverStripe tries to protect users against *Cross-Site Request Forgery (CSRF)*
|
||||||
parameter to each form. See [secure-development](/topics/security) for details.
|
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`)
|
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()]`.
|
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
|
:::php
|
||||||
class MyCustomForm extends MyForm {
|
class MyCustomForm extends MyForm {
|
||||||
|
|
||||||
public function __construct($controller, $name) {
|
public function __construct($controller, $name) {
|
||||||
parent::__construct($controller, $name);
|
parent::__construct($controller, $name);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user