developer guides / models

This commit is contained in:
Uncle Cheese 2014-10-28 16:45:46 +13:00 committed by Cam Findlay
parent e448fb9434
commit f83c8f0821
8 changed files with 90 additions and 34 deletions

View File

@ -1,14 +1,14 @@
title: Introduction to the Data Model and ORM
summary: Introduction to creating and querying a Data Model through the ORM.
summary: Introduction to creating and querying a database records through the ORM (object-relational model)
# Introduction to the Data Model and ORM
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) to represent it's
SilverStripe uses an [object-relational model](http://en.wikipedia.org/wiki/Object-relational_model) to represent its
information.
* 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.
* 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]. The [api:DataObject] class represents a
single row in a database table, following the ["Active Record"](http://en.wikipedia.org/wiki/Active_record_pattern)
@ -38,8 +38,8 @@ so on. After writing this class, we need to regenerate the database schema.
## Generating the Database Schema
After adding, modifying or removing `DataObject` classes make sure to rebuild your SilverStripe database. The
database-schema is generated automatically by visiting the URL http://www.yoursite.com/dev/build.
After adding, modifying or removing `DataObject` subclasses, make sure to rebuild your SilverStripe database. The
database schema is generated automatically by visiting the URL http://www.yoursite.com/dev/build while authenticated as an administrator.
This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema
as required.
@ -54,9 +54,9 @@ It will perform the following changes:
It **won't** do any of the following
* Deleting tables
* Deleting fields
* Rename any tables that it doesn't recognize - so other applications can co-exist in the same database, as long as
* Delete tables
* Delete fields
* Rename any tables that it doesn't recognize. This allows other applications to coexist in the same database, as long as
their table names don't match a SilverStripe data class.
@ -117,6 +117,11 @@ Or, a better way is to use the `create` method.
:::php
$player = Player::create();
<div class="notice" markdown='1'>
Using the `create()` method provides chainability, which can create add elegance and brevity to your code, e.g. `Player::create()->write()`. More importantly, however, it will look up the class in the [Injector](../extending/injector) so that can the class can be overriden by [dependency injection](http://en.wikipedia.org/wiki/Dependency_injection).
</div>
Database columns and properties can be set as class properties on the object. The SilverStripe ORM handles the saving
of the values through a custom `__set()` method.
@ -124,12 +129,18 @@ of the values through a custom `__set()` method.
$player->FirstName = "Sam";
$player->PlayerNumber = 07;
To save the `DataObject` to the database use the `write()` method. The first time `write()` is called an `ID` will be
To save the `DataObject` to the database, use the `write()` method. The first time `write()` is called, an `ID` will be
set.
:::php
$player->write();
For convenience, the `write()` method returns the record's ID. This is particularly useful when creating new records.
:::php
$player = Player::create();
$id = $player->write();
## Querying Data
With the `Player` class defined we can query our data using the `ORM` or Object-Relational Model. The `ORM` provides
@ -166,7 +177,7 @@ Provided `filter` values are automatically escaped and do not require any escapi
The `ORM` doesn't actually execute the [api:SQLQuery] until you iterate on the result with a `foreach()` or `<% loop %>`.
It's smart enough to generate a single efficient query at the last moment in time without needing to post process the
It's 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
:::php
@ -202,6 +213,15 @@ This also means that getting the count of a list of objects will be done with a
echo $player->FirstName;
}
Notice that we can step into the loop safely without having to check if `$players` exists. The `get()` call is robust, and will at worst return an empty `DataList` object. If you do want to check if the query returned any records, you can use the `exists()` method, e.g.
:::php
$players = Player::get();
if($players->exists()) {
// do something here
}
See the [Lists](../lists) documentation for more information on dealing with [api:SS_List] instances.
## Returning a single DataObject
@ -279,7 +299,7 @@ So, this would return only those players called "Sam Minnée".
// SELECT * FROM Player WHERE FirstName = 'Sam' AND LastName = 'Minnée'
There is also a short hand way of getting Players with the FirstName of Sam.
There is also a shorthand way of getting Players with the FirstName of Sam.
:::php
$players = Player::get()->filter('FirstName', 'Sam');
@ -342,7 +362,9 @@ It is also possible to filter by a PHP callback, this will force the data model
PHP, thus `filter()` or `filterAny()` are to be preferred over `filterByCallback()`.
<div class="notice" markdown="1">
Because `filterByCallback()` has to run in PHP, it will always return an `ArrayList`
Because `filterByCallback()` has to run in PHP, it has a significant performance tradeoff, and should not be used on large recordsets.
`filterByCallback()` will always return an `ArrayList`.
</div>
The first parameter to the callback is the item, the second parameter is the list itself. The callback will run once
@ -436,8 +458,8 @@ Note that the `limit` argument order is different from a MySQL LIMIT clause.
### Raw SQL
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.
methods that manipulate the SQL query at a lower level. When using these, please ensure that all table and field names
are escaped with double quotes, otherwise some DB backends (e.g. PostgreSQL) won't work.
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.
@ -548,7 +570,7 @@ special fields. This is called the "base table". In our case, `SiteTree` is the
sub-classes of the base class (including the base class itself).
* Each sub-class of the base object will also be given its own table, *as long as it has custom fields*. In the
example above, NewsSection didn't have its own data and so an extra table would be redundant.
example above, NewsSection didn't have its own data, so an extra table would be redundant.
* In all the tables, ID is the primary key. A matching ID number is used for all parts of a particular record:
record #2 in Page refers to the same object as record #2 in `[api:SiteTree]`.

View File

@ -1,5 +1,5 @@
title: Relations between Records
summary: Relate models together using the ORM.
summary: Relate models together using the ORM using has_one, has_many, and many_many.
# Relations between Records
@ -39,6 +39,8 @@ A 1-to-1 relation creates a database-column called "`<relationship-name>`ID", in
This defines a relationship called `Team` which links to a `Team` class. The `ORM` handles navigating the relationship
and provides a short syntax for accessing the related object.
At the database level, the `has_one` creates a `TeamID` field on `Player`. A `has_many` field does not impose any database changes. It merely injects a new method into the class to access the related records (in this case, `Players()`)
:::php
$player = Player::get()->byId(1);
@ -59,8 +61,7 @@ The relationship can also be navigated in [templates](../templates).
## has_many
Defines 1-to-many joins. A database-column named ""`<relationship-name>`ID"" will to be created in the child-class. As
you can see from the previous example, `$has_many` goes hand in hand with `$has_one`.
Defines 1-to-many joins. As you can see from the previous example, `$has_many` goes hand in hand with `$has_one`.
<div class="alert" markdown='1'>
Please specify a $has_one-relationship on the related child-class as well, in order to have the necessary accessors
@ -184,7 +185,7 @@ available on both ends.
);
}
Much like the `has_one` relationship, `mant_many` can be navigated through the `ORM` as well. The only difference being
Much like the `has_one` relationship, `many_many` can be navigated through the `ORM` as well. The only difference being
you will get an instance of [api:ManyManyList] rather than the object.
:::php
@ -203,6 +204,11 @@ The relationship can also be navigated in [templates](../templates).
<% end_if %>
<% end_with %>
## many_many or belongs_many_many?
If you're unsure about whether an object should take on `many_many` or `belongs_many_many`, the best way to think about it is that the object where the relationship will be edited (i.e. via checkboxes) should contain the `many_many`. For instance, in a `many_many` of Product => Categories, the `Product` should contain the `many_many`, because it is much more likely that the user will select Categories for a Product than vice-versa.
## 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

View File

@ -1,5 +1,5 @@
title: Managing Lists
summary: Learn how to manipulate SS_List objects.
summary: The SS_List interface allows you to iterate through and manipulate a list of objects.
# Managing Lists
@ -9,7 +9,7 @@ modify.
## Iterating over the list.
[api:SS_List] implements `IteratorAggregate` allowing you to loop over the instance.
[api:SS_List] implements `IteratorAggregate`, allowing you to loop over the instance.
:::php
$members = Member::get();

View File

@ -1,5 +1,5 @@
title: Data Types, Overloading and Casting
summary: Documentation on how data is stored going in, coming out of the ORM and how to modify it.
summary: Learn how how data is stored going in and coming out of the ORM and how to modify it.
# Data Types and Casting
@ -7,7 +7,7 @@ Each model in a SilverStripe [api:DataObject] will handle data at some point. Th
the ones defined in a `$db` array or simply a method that returns data for the template.
A Data Type is represented in SilverStripe by a [api:DBField] subclass. The class is responsible for telling the ORM
about how to store it's data in the database and how to format the information coming out of the database.
about how to store its data in the database and how to format the information coming out of the database, i.e. on a template.
In the `Player` example, we have four database columns each with a different data type (Int, Varchar).

View File

@ -12,7 +12,7 @@ data records.
## onBeforeWrite
You can customize saving-behaviour for each DataObject, e.g. for adding workflow or data customization. The function is
You can customize saving-behavior 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.

View File

@ -32,11 +32,11 @@ An example of a `SearchFilter` in use:
Developers can define their own [api:SearchFilter] 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
`":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 starts with S either lower or uppercase
The following is a query which will return everyone whose first name starts with "S", either lowercase or uppercase:
:::php
$players = Player::get()->filter(array(

View File

@ -3,7 +3,7 @@ summary: Add versioning to your database content through the Versioned extension
# Versioning
Database content in SilverStripe can be "staged" before its publication, as well as tracking all changes through the
Database content in SilverStripe can be "staged" before its publication, as well as track all changes through the
lifetime of a database record.
It is most commonly applied to pages in the CMS (the `SiteTree` class). Draft content edited in the CMS can be different
@ -148,8 +148,16 @@ helpers.
### Templates Variables
In templates, you don't need to worry about this distinction. The `$Content` variable contain the published content by
default, and only preview draft content if explicitly requested (e.g. by the "preview" feature in the CMS). If you want
to force a specific stage, we recommend the `Controller->init()` method for this purpose.
default, and only preview draft content if explicitly requested (e.g. by the "preview" feature in the CMS, or by adding ?stage=Stage to the URL). If you want
to force a specific stage, we recommend the `Controller->init()` method for this purpose, for example:
**mysite/code/MyController.php**
:::php
public function init() {
parent::init();
Versioned::set_reading_mode('Stage.Stage');
}
### Controllers

View File

@ -1,5 +1,5 @@
title: Building Model and Search Interfaces around Scaffolding
summary: Model Driven approach to defining your application UI.
summary: A Model-driven approach to defining your application UI.
# Scaffolding
@ -24,7 +24,7 @@ An example is `DataObject`, SilverStripe will automatically create your CMS inte
);
public function getCMSFields() {
// parent::getCMSFields() does all the hard work and creates the fields for title, isactive and content.
// parent::getCMSFields() does all the hard work and creates the fields for Title, IsActive and Content.
$fields = parent::getCMSFields();
$fields->fieldByName('IsActive')->setTitle('Is active?');
@ -32,6 +32,26 @@ An example is `DataObject`, SilverStripe will automatically create your CMS inte
}
}
To fully customize your form fields, start with an empty FieldList.
:::php
<?php
public function getCMSFields() {
$fields = FieldList::create(
TabSet::create("Root",
CheckboxSetField::create('IsActive','Is active?'),
TextField::create('Title'),
TextareaField::create('Content')
->setRows(5)
)
);
return $fields;
}
You can also alter the fields of built-in and module `DataObject` classes through your own
[DataExtension](../extensions), and a call to `DataExtension->updateCMSFields`.
@ -125,7 +145,7 @@ To include relations (`$has_one`, `$has_many` and `$many_many`) in your search,
### Summary Fields
Summary fields can be used to show a quick overview of the data for a specific [api:DataObject] record. Most common use
Summary fields can be used to show a quick overview of the data for a specific [api:DataObject] record. The most common use
is their display as table columns, e.g. in the search results of a `[api:ModelAdmin]` CMS interface.
:::php