Models support several relationship types:
has_one
- A one-to-one relationship between two models, with the second one referencing the first.has_many
- A one-to-many relationship between two models, with the second one referencing the first.belongs_to
- The reverse of has_one
or has_many
relationships.many_many
- A many-to-many relationship between two models, with a join table between them.Below are examples of each relationship type.
A has_one
relationship is used when you have two database tables, with the second one referencing the first, and you only want one record in the second table for each record in the first.
For example, say we have a gallery
table and a cover
image table to store additional fields related to the cover image (to keep the gallery
table nice and slim).
create table gallery (
id integer primary key,
name char(32) not null
);
create table cover (
id integer primary key,
gallery int not null,
image char(72) not null
);
A gallery only needs one cover image, so we use a has_one
relationship to say ""A gallery has_one cover"" in our models, which we can describe as follows:
<?php
class Gallery extends Model {
public $fields = array (
'cover' => array ('has_one' => 'Cover')
);
}
?>
This will create a dynamic method cover()
that will retrieve the Cover
object associated with the current gallery. For example:
<?php
$gallery = new Gallery (1);
$cover = $gallery->cover ();
echo $cover->image;
?>
Note: We will define the
Cover
model in thebelongs_to
section below.
Let's add an item table to our schema:
create table item (
id integer primary key,
gallery int not null,
name char(32) not null
);
A gallery will have multiple items, so we can update our Gallery
model and add a has_many
relationship to Item
(which we'll define in the next section):
<?php
class Gallery extends Model {
public $fields = array (
'cover' => array ('has_one' => 'Cover'),
'items' => array ('has_many' => 'Item')
);
}
?>
Here we used the plural items
, so the dynamic method will be items()
and can be used like this:
<?php
$gallery = new Gallery (1);
$items = $gallery->items ();
foreach ($items as $item) {
printf ('%s<br />', $item->name);
}
?>
Note: By default, models will assume the field name in the
cover
anditem
tables is the same as thegallery
table name. To override this, add a'field_name' => 'gallery_id'
to the field definition with the real field name:
<?php
class Gallery extends Model {
public $fields = array (
'cover' => array (
'has_one' => 'Cover',
'field_name' => 'gallery_id'
),
'items' => array (
'has_many' => 'Item',
'field_name' => 'gallery_id'
)
);
}
?>
Now it's time to create our Cover
and Item
models, which will use a belongs_to
relation, which is the reverse of the has_one
and has_many
relations.
<?php
class Cover extends Model {
public $fields = array (
'gallery' => array ('belongs_to' => 'Gallery')
);
}
?>
And for the Item
model:
<?php
class Item extends Model {
public $fields = array (
'gallery' => array ('belongs_to' => 'Gallery')
);
}
?>
Note: By default, models will assume the field name is the same as the method name in a
belongs_to
relation, which is the key in the$fields
array. You can override this in the same way as before by setting a customfield_name
value.
Now that we've described both sides of the relationship, we can fetch the Gallery
belonging to any Cover
or Item
object like this:
<?php
$item = new Item (1);
$gallery = $item->gallery ();
echo $gallery->name;
$cover = new Cover (1);
$gallery = $cover->gallery ();
echo $gallery->name;
?>
We can even chain them if we wish:
<?php
$cover = new Cover (1);
$items = $cover->gallery ()->items ();
foreach ($items as $item) {
printf ('%s<br />', $item->name);
}
?>
Many-to-many relationships describe tables that can have multiple records in both that reference a single record in either. They do so using an intermediate table to join them.
Here's an example using books and authors:
create table author (
id integer primary key,
name char(32)
);
create table book (
id integer primary key,
name char(32)
);
create table book_author (
book int not null,
author int not null
);
The book_author
table references both the book
and the author
tables and can contain any combination of IDs from either.
Here's how we can model that with Elefant for the Author
model:
<?php
class Author extends Model {
public $fields = array (
'books' => array (
'many_many' => 'Book',
'join_table' => 'book_author'
)
);
}
?>
And the Book
model is the reverse of Author
:
<?php
class Book extends Model {
public $fields = array (
'authors' => array (
'many_many' => 'Author',
'join_table' => 'book_author'
)
);
}
?>
Now we can query in either direction as easy as this:
<?php
$author = new Author (1);
$books = $author->books ();
$book = new Book (1);
$authors = $book->authors ();
?>
Note: The field names will be inferred by the names of the two main tables. If you want to override them, you can specify them like this:
<?php
class Author extends Model {
public $fields = array (
'books' => array (
'many_many' => 'Book',
'join_table' => 'book_author',
'this_field' => 'author_id',
'that_field' => 'book_id'
)
);
}
?>
Model relationships can specify an order_by
value to specify how you want results sorted in a has_many
or many_many
relationship. It works like this:
<?php
class Gallery extends Model {
public $fields = array (
'items' => array (
'has_many' => 'Item',
'order_by' => array ('name', 'asc')
)
);
}
?>
Now whenever you call $gallery->items ()
the results will be sorted alphabetically for you.
By default, the first time one of these dynamic methods is called, the result is memoized so the next call can simply return the same value without hitting the database again and again.
This works well most of the time, since PHP requests are short-lived and avoiding repeated calls to the database can be a big performance win. But sometimes you need to get a fresh copy, in case your database was updated elsewhere in the application. To do this, simply pass a boolean true value to the dynamic method call:
<?php
$gallery = new Gallery (1);
$items = $gallery->items ();
// these results were memoized
$items = $gallery->items ();
// these are fresh from the database
$items = $gallery->items (true);
?>
This optimizes for performance first, but gives you the best of both worlds.