- CakePHP 1.3 Application Development Cookbook
- Mariano Iglesias
- 1922字
- 2021-04-09 22:04:16
This recipe shows how to use Containable
to specify what related models are returned as a result of a find
operation. It also shows us how to limit which fields are obtained for each association.
To go through this recipe we need some sample tables to work with.
- Create a table named
families
, using the following SQL statement:CREATE TABLE `families`( `id` INT UNSIGNED AUTO_INCREMENT NOT NULL, `name` VARCHAR(255) NOT NULL, PRIMARY KEY(`id`) );
- Create a table named
people
, using the following SQL statement:CREATE TABLE `people`( `id` INT UNSIGNED AUTO_INCREMENT NOT NULL, `family_id` INT UNSIGNED NOT NULL, `name` VARCHAR(255) NOT NULL, `email` VARCHAR(255) NOT NULL, PRIMARY KEY(`id`), KEY `family_id`(`family_id`), CONSTRAINT `people__families` FOREIGN KEY(`family_id`) REFERENCES `families`(`id`) );
- Create a table named
profiles
, using the following SQL statement:CREATE TABLE `profiles`( `id` INT UNSIGNED AUTO_INCREMENT NOT NULL, `person_id` INT UNSIGNED NOT NULL, `website` VARCHAR(255) default NULL, `birthdate` DATE default NULL, PRIMARY KEY(`id`), KEY `person_id`(`person_id`), CONSTRAINT `profiles__people` FOREIGN KEY(`person_id`) REFERENCES `people`(`id`) );
- Create a table named
posts
, using the following SQL statement:CREATE TABLE `posts`( `id` INT UNSIGNED AUTO_INCREMENT NOT NULL, `person_id` INT UNSIGNED NOT NULL, `title` VARCHAR(255) NOT NULL, `body` TEXT NOT NULL, `created` DATETIME NOT NULL, `modified` DATETIME NOT NULL, PRIMARY KEY(`id`), KEY `person_id`(`person_id`), CONSTRAINT `posts__people` FOREIGN KEY(`person_id`) REFERENCES `people`(`id`) );
Note
Even if you do not want to add foreign key constraints to your tables, make sure you use KEYs for each field that is a reference to a record in another table. By doing so, you will significantly improve the speed of your SQL queries when the referenced tables are joined.
- Add some sample data, using the following SQL statements:
INSERT INTO `families`(`id`, `name`) VALUES (1, 'The Does'); INSERT INTO `people`(`id`, `family_id`, `name`, `email`) VALUES (1, 1, 'John Doe', 'john.doe@example.com'), (2, 1, 'Jane Doe', 'jane.doe@example.com'); INSERT INTO `profiles`(`person_id`, `website`, `birthdate`) VALUES (1, 'http://john.example.com', '1978-07-13'), (2, NULL, '1981-09-18'); INSERT INTO `posts`(`person_id`, `title`, `body`, `created`, `modified`) VALUES (1, 'John\'s Post 1', 'Body for John\'s Post 1', NOW(), NOW()), (1, 'John\'s Post 2', 'Body for John\'s Post 2', NOW(), NOW());
- We need
Containable
added to all our models, so follow the recipe Adding Containable to all models. - We proceed now to create the main model. Create a file named
person.php
and place it in yourapp/models
folder with the following contents:<?php class Person extends AppModel { public $belongsTo = array('Family'); public $hasOne = array('Profile'); public $hasMany = array('Post'); } ?>
- Create the model
Family
in a file namedfamily.php
and place it in yourapp/models
folder with the following contents:<?php class Family extends AppModel { public $hasMany = array('Person'); } ?>
When Containable
is available for our models, we can add a setting to the find
operation called contain
. In that setting we specify, in an array-based hierarchy, the associated data we want returned. A special value contain
can receive is false
, or an empty array, which tells Containable
not to return any associated data.
For example, to get the first Person
record without associated data, we simply do:
$person = $this->Person->find('first', array( 'contain' => false ));
If we want to obtain the first Person
record together with the Family
they belong to, we do:
$person = $this->Person->find('first', array( 'contain' => array('Family') ));
Using our sample data, the above query will result in the following array structure:
array( 'Person' => array( 'id' => '1', 'family_id' => '1', 'name' => 'John Doe', 'email' => 'john.doe@example.com' ), 'Family' => array( 'id' => '1', 'name' => 'The Does' ) )
Let's say that now we also want to obtain all Post
records for the person and all members in the family that Person
belongs to. We would then have to do:
$person = $this->Person->find('first', array( 'contain' => array( 'Family.Person' 'Post' ) ));
The above would result in the following array structure (the created
and modified
fields have been removed for readability):
array( 'Person' => array( 'id' => '1', 'family_id' => '1', 'name' => 'John Doe', 'email' => 'john.doe@example.com' ), 'Family' => array( 'id' => '1', 'name' => 'The Does', 'Person' => array( array( 'id' => '1', 'family_id' => '1', 'name' => 'John Doe', 'email' => 'john.doe@example.com' ), array( 'id' => '2', 'family_id' => '1', 'name' => 'Jane Doe', 'email' => 'jane.doe@example.com' ) ) ), 'Post' => array( array( 'id' => '1', 'person_id' => '1', 'title' => 'John\'s Post 1', 'body' => 'Body for John\'s Post 1' ), array( 'id' => '2', 'person_id' => '1', 'title' => 'John\'s Post 2', 'body' => 'Body for John\'s Post 2' ) ) )
We can also use Containable
to specify which fields from a related model we want to fetch. Using the preceding sample, let's limit the Post
fields so we only return the title
and the Person
records for the person's Family
, so we only return the name
field. We do so by adding the name of the field to the associated model hierarchy:
$person = $this->Person->find('first', array( 'contain' => array( 'Family.Person.name', 'Post.title' ) ));
The returned data structure will then look like this:
array( 'Person' => array( 'id' => '1', 'family_id' => '1', 'name' => 'John Doe', 'email' => 'john.doe@example.com' ), 'Family' => array( 'id' => '1', 'name' => 'The Does', 'Person' => array( array( 'name' => 'John Doe', 'family_id' => '1', 'id' => '1' ), array( 'name' => 'Jane Doe', 'family_id' => '1', 'id' => '2' ) ) ), 'Post' => array( array( 'title' => 'John\'s Post 1', 'id' => '1', 'person_id' => '1' ), array( 'title' => 'John\'s Post 2', 'id' => '2', 'person_id' => '1' ) ) )
You may notice that even when we indicated specific fields for the Family => Person
binding, and for the Post
binding, there are some extra fields being returned. Those fields (such as family_id
) are needed by CakePHP, and known as foreign key fields, to fetch the associated data, so Containable
is smart enough to include them in the query.
Let us say that we also want a person's e-mail. As there is more than a field needed, we will need to use the array notation, using the fields
setting to specify the list of fields:
$person = $this->Person->find('first', array( 'contain' => array( 'Family' => array( 'Person' => array( 'fields' => array('email', 'name') ) ), 'Post.title' ) ));
We use the contain
find setting to specify what type of containment we want to use for the find operation. That containment is given as an array, where the array hierarchy mimics that of the model relationships. As the hierarchy can get deep enough to make array notation complex to deal with, the dot notation used throughout this recipe serves as an useful and more readable alternative.
If we want to refer to the model Person
that belongs to the model Family
, the proper contain
syntax for that is Person => Family
(we can also use Person.Family
, which is more concise.)
We also use the fields
setting to specify which fields we want fetched for a binding. We do that by specifying an array of field names as part of the binding Containable
setting.
Containable
looks for the contain
find setting right before we issue a find operation on a model. If it finds one, it alters the model bindings to be returned by issuing unbindModel()
calls on the appropriate models to unbind those relationships that are not specified in the contain
find setting. It then sets the recursive
find setting to the minimum value required to fetch the associated data.
Let us use a practical example to further understand this wrapping process. Using our Person
model (which has a belongsTo
relationship to Family
, a hasOne
relationship to Profile
, and a hasMany
relationship to Post)
, the following Containable
based query:
$person = $this->Person->find('first', array( 'contain' => array('Family.Person') ));
or the same query using array notation:
$person = $this->Person->find('first', array( 'contain' => array('Family' => 'Person') ));
is equivalent to the following set of instructions, which do not use Containable
, but the built in unbindModel()
method available in CakePHP's Model
class:
$this->Person->unbindModel(array( 'hasOne' => array('Profile'), 'hasMany' => array('Post') )); $person = $this->Person->find('first', array( 'recursive' => 2 ));
Not using Containable
is not only much more complicated, but can also pose a problem if we decide to alter some of our relationships. In the preceding example, if we decide to remove the Profile
binding, or change its relationship type, we would have to modify the unbindModel()
call. However, if we are using Containable
, the same code applies, without us having to worry about such changes.
We have seen how to use the contain
find parameter to limit which bindings are returned after a find
operation. Even when its format seems self-explanatory, let us go through another example to have a deeper understanding of Containable's
array notation. Assume that we have the models and relationships shown in the following diagram:

Transforming that diagram to something the Containable
behavior understands is as simple as writing it using an array structure. For example, if we are issuing a find
operation on the User
model and we want to refer to the Profile
relationship, a simple array('Profile')
expression would suffice, as the Profile
model is directly related to the User
model.
If we want to refer to the Comment
relationship for the Article
records the User
is an owner of, which belongs to an Article
that itself belongs to our User
model, then we add another dimension to the structure, which is now represented as array('Article' => 'Comment')
.
We can already deduce how the next example will look like. Assume we want to obtain the Comment
together with the Profile
of the User
that commented on each Article
. The structure will then look like: array('Article' => array('Comment' => array('User' => 'Profile')))
.
Sometimes we want to simplify the readability, and fortunately the Containable
behavior allows the above expression to be rewritten as array('Article.Comment.User.Profile')
, which is known as dot notation. However, if you want to change other parameters to the binding, then this syntax would have to be changed to the full array-based expression (see section See also in this recipe).
When you issue a find operation that uses the Containable
behavior to change some of its bindings, CakePHP will reset all bindings' changes to their original states, once the find is completed. This is what is normally wanted on most cases, but there are some scenarios where you want to keep your changes until you manually reset them, such as when you need to issue more than one find operation and have all those finds use the modified bindings.
To force our binding changes to be kept, we use the reset
option in the contain
find parameter, setting it to false
. When we are ready to reset them, we issue a call to the resetBindings()
method added by the Containable
behavior to our model. The following sample code shows this procedure:
$person = $this->Person->find('first', array( 'contain' => array( 'reset' => false, 'Family' ) )); // ... $this->Person->resetBindings();
Another way to achieve the same result is by calling the contain()
method (setting its first argument to the contained bindings, and its second argument to false
to indicate that we wish to keep these containments), available to all models that use Containable
, issue the find (without, need to use the contain
setting), and then reset the bindings:
$this->Person->contain(array('Family'), false); $person = $this->Person->find('first'); // ... $this->Person->resetBindings();
- ANSYS 14熱力學(xué)·電磁學(xué)·耦合場(chǎng)分析自學(xué)手冊(cè)
- Illustrator實(shí)例教程:Illustrator 2021(電子活頁(yè)微課版)
- Solid Works 2021產(chǎn)品設(shè)計(jì)標(biāo)準(zhǔn)教程
- Maya 2019三維動(dòng)畫(huà)基礎(chǔ)案例教程
- Photoshop日系少女寫(xiě)真后期解密
- Moodle JavaScript Cookbook
- iPhone JavaScript Cookbook
- After Effects CC 2018影視特效與合成案例教程
- 通達(dá)信炒股軟件從入門(mén)到精通(第2版)
- 用Cubase輕松制作你的短視頻音樂(lè)
- Photoshop CS6 圖像處理項(xiàng)目任務(wù)教程
- 陌上花開(kāi):古風(fēng)CG插畫(huà)繪制技法精解(花卉篇)
- Photoshop CC 2017 淘寶美工設(shè)計(jì)實(shí)例教程
- Building Websites with Mambo
- 中文版3ds Max 2012基礎(chǔ)培訓(xùn)教程(第2版)