Model Relationships
li3’s data layer offers a way to facilitate data relationships and structure. This guide is meant to show you how to specify and define these data relationships and use them to your advantage as you build your application.
Before you define a model relationship in your code, it’s important to understand the terminology that describes a relationship between two objects in your system. In li3 (and elsewhere), a specific set of terms are used to describe model relationships:
- hasOne: the current object is linked to a single object of another type
- hasMany: the current object is linked to many objects of another type
- belongsTo: the current object is owned and marked as related to another object
If you’re having a hard time remembering hasOne/hasMany versus belongsTo, just
remember this: if the current model contains some sort of marker (like a foreign key), it
belongsTo another model.
Defining this object relationship is simple: you populate special properties on the model object. For example, let’s say we’re building an online store. Each is filled with many Product
objects. In this case, we’d want to specify Category
hasMany Product
. Let’s see how this is done:
This simple declaration relies on convention, and is the functional equivalent to this:
class Categories extends \lithium\data\Model {
public $hasMany = ['Products' => [
'to' => 'Products',
'key' => 'category_id',
'constraints' => [],
'fields' => [],
'order' => null,
]];
}
All of the model relationships use these same keys (although there’s no reason to order or limit hasOne or belongsTo) and can be configured likewise.
Once a relationship as been configured, you can use your models to fetch related data. We can now do this in a controller:
Notice the new with
key supplied to the model? This tells the model that you want related data joined to the normal result.
As you can see from the output, the related data has been added on to the model result. While we’re printing out array contents here, you can as easily loop through or access the same information at in this case as well.
Good practice is to qualify fields (with the model name) in such queries to make the
statement unambigous.
Categories::find('all', [
'with' => 'Products'
]);
Categories::find('all', [
'with' => 'Products',
'order' => ['Categories.title']
]);
To have the nested products themselves sorted inside the ->products
, you
add the qualified relationship field to the order statement.
Did you note that we also added Categories.id
in there? This is to keep
a consistent result set.
Sorting the whole result set just by the products
price alone is not possible.
If you see an exception thrown (Associated records hydrated out of order.
), than
simply order by the main models primary key first. Other frameworks will magically
rewrite the query for you and add that primary key automatically. li3 however has
decided against this, as it doesn’t want to mess with your queries.
namespace app\controllers;
use \app\models\Products;
use \app\models\Categories;
public function create() {
// If form data has been submitted, create a new Product.
if ($this->request->data) {
Products::create($this->request->data)->save();
}
// Create a list of categories to send to the view.
$categories = Categories::find('all');
$categoryList = [];
foreach ($categories as $category) {
$categoryList[$category->id] = $category->name;
}
$this->set(compact('categoryList'));
}
And, here is the view that contains the form: