Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

 

SilverStripe 3’s new ORM

Sam Minnée, Chief Executive Officer and co-founder of SilverStripe, has shaped the SilverStripe Suite and...

Comments 10

by Sam Minnée

Posted 9 November 2011

Read post

Sam Minnée, Chief Executive Officer and co-founder of SilverStripe, has shaped the SilverStripe Suite and is part of its success story as an internationally respected Open Source CMS.

One of the parts of SilverStripe 3 that I was most heavily involved with was the development of the new ORM. ORM stands for “Object-relational mapper” and it’s the part of the system that turns your DataObject::get() calls into SQL queries, and constructs relevant DataObjects with the results. In other words, it maps our PHP objects to a relational database for storage.

The biggest change we’ve made to the ORM in SilverStripe 3 is how it fetches sets of records. In SilverStripe 2, if you wanted to query some fields you would call this:

$pages = DataObject::get(‘SiteTree’, ‘“ParentID” = 5’);

The query would be executed straight away. On the face of it, this might not be such a problem, and for simple cases it works okay.

But what about if I just want to get the number of records that match that query? Maybe I would call this: 

$pages = DataObject::get(‘SiteTree’, ‘“ParentID” = 5’);
$count = $pages ? $pages->Count() : 0;

However, in SilverStripe 2 what that would do is query every single matching record from the database, create a DataObject for them, get the size of, and then throw all of those objects away. This is very inefficient. 

As a result, a more common way to get the number of records was to run something like this: 

$count = DB::query(‘SELECT COUNT(*) FROM “SiteTree” WHERE
“ParentID” = 5’)->value();

This is more efficient, but it bypasses the ORM and is really just an admission that the ORM isn’t giving us what we need.

In SilverStripe 3, a DataObject::get() call won’t query the database immediately. Instead, it will create an internal representation of that ORM query and save it for later. There is a new class for storing these, called DataList. It will only call the query once a result is needed. For example:

// Query isn’t executed here 
$pages = DataObject::get(‘SiteTree’, ‘“ParentID” = 5’);
// Query is executed here
foreach($pages as $page) echo “<li>$page->Title</li>”;

As part of these changes, we have made a new API better indicate that you are creating an option that represents the query, rather than querying straight away. The old syntax still works:

DataObject::get(‘SiteTree’, ‘“ParentID” = 5’);

But, going forward, we recommend this new syntax: 

DataList::create(‘SiteTree’)->where(‘“ParentID” = 5’);

Another advantage of this syntax is that you don’t need to remember the order of all the DataObject::get() arguments.

What’s more, when you call the Count() method, it won’t call the full query at all. Instead it will create a suitable “SELECT COUNT(*) …” query based on the original query. As a result, this code is now much more efficient: 

$count = DataList::create(“SiteTree”)->where(‘“ParentID” = 5’)->Count();

So, efficiency is a benefit, but the new ORM also lets us make our code more loosely coupled and easier to maintain. We can pass DataLists to other parts of the system, without needing to explain how that DataList has been created. For example, ComplexTableField has a large number of configuration fields for specifying the data that should be displayed. In SilverStripe 3, we are replacing ComplexTableField with GridField, which takes one parameter for specifying data: a DataList. GridField doesn’t need to know the query behind that DataList. Instead, it can just call limit(), sort(), and filter() to limit the query to the appropriate page of results.

There’s more to the new ORM than what I can cover in a single blog post. You can find out more on the doc site. If you would like to try this out right now, you can download SilverStripe 3.0.0-alpha1.