240 lines
9.7 KiB
Plaintext
240 lines
9.7 KiB
Plaintext
++ Dealing with relations
|
|
++ Many-to-Many relations
|
|
+++ Creating a new link
|
|
Lets say we have two classes User and Group which are linked trhough a GroupUser association class. When working with transient (new) records the fastest way for adding a User and couple of Groups for it is:
|
|
<code type="php">
|
|
$user = new User();
|
|
$user->name = 'Some User';
|
|
$user->Group[0]->name = 'Some Group';
|
|
$user->Group[1]->name = 'Some Other Group';
|
|
$user->save();
|
|
</code>
|
|
|
|
However in real world scenarious you often already have existing groups, where you want to add a given user. The most efficient way of doing this is:
|
|
<code type="php">
|
|
$gu = new GroupUser();
|
|
$gu->user_id = $userId;
|
|
$gu->group_id = $groupId;
|
|
$gu->save();
|
|
</code>
|
|
|
|
+++ Deleting a link
|
|
|
|
The right way to delete links between many-to-many associated records is by using the DQL DELETE statement. Convenient and recommended way of using DQL DELETE is trhough the Query API.
|
|
|
|
<code type="php">
|
|
$deleted = Doctrine_Query::create()
|
|
->delete()
|
|
->from('GroupUser')
|
|
->addWhere('user_id = 5')
|
|
->whereIn('group_id', $groupIds);
|
|
->execute();
|
|
// print out the deleted links
|
|
print $deleted;
|
|
</code>
|
|
|
|
Another way to {{unlink}} the relationships between related objects is through the {{Doctrine_Record::unlink}} method. However, you should avoid using this method unless you already have the parent model, since it involves querying the database first.
|
|
|
|
<code type="php">
|
|
$user = $conn->getTable('User')->find(5);
|
|
$user->unlink('Group', array(0, 1));
|
|
$user->save();
|
|
|
|
// you can also unlink ALL relationships to Group
|
|
$user->unlink('Group');
|
|
</code>
|
|
|
|
While the obvious and convinient way of deleting a link between User and Group would be the following, you still should *NOT* do this:
|
|
|
|
<code type="php">
|
|
$user = $conn->getTable('User')->find(5);
|
|
$user->GroupUser
|
|
->remove(0)
|
|
->remove(1);
|
|
$user->save();
|
|
</code>
|
|
|
|
This is due to a fact that $user->GroupUser loads all group links for given user. This can time-consuming task if user belongs to many groups. Even if the user belongs to few groups this will still execute an unnecessary SELECT statement.
|
|
|
|
++ Fetching objects
|
|
|
|
Normally when you fetch data from database the following phases are executed:
|
|
|
|
1. Sending the query to database
|
|
2. Retrieve the returned data from the database
|
|
|
|
In terms of object fetching we call these two phases the 'fetching' phase. Doctrine also has another phase called hydration phase. The hydration phase takes place whenever you are fecthing structured arrays / objects. Unless explicitly specified everything in Doctrine gets hydrated.
|
|
|
|
Lets consider we have users and phonenumbers with their relation being one-to-many. Now consider the following plain sql query:
|
|
|
|
<code type="php">
|
|
$dbh->fetchAll('SELECT u.id, u.name, p.phonenumber FROM user u LEFT JOIN phonenumber p ON u.id = p.user_id');
|
|
</code>
|
|
|
|
If you are familiar with these kind of one-to-many joins it may be familiar to you how the basic result set is constructed. Whenever the user has more than one phonenumbers there will be duplicated data in the result set. The result set might look something like:
|
|
|
|
<code>
|
|
index | u.id | u.name | p.phonenumber |
|
|
0 | 1 | Jack Daniels | 123 123 |
|
|
1 | 1 | Jack Daniels | 456 456 |
|
|
2 | 2 | John Beer | 111 111 |
|
|
3 | 3 | John Smith | 222 222 |
|
|
4 | 3 | John Smith | 333 333 |
|
|
5 | 3 | John Smith | 444 444 |
|
|
</code>
|
|
|
|
Here Jack Daniels has 2 phonenumbers, John Beer has one whereas John Smith has 3 phonenumbers. You may notice how clumsy this result set is. Its hard to iterate over it as you would need some duplicate data checkings here and there.
|
|
|
|
Doctrine hydration removes all duplicated data. It also performs many other things such as:
|
|
|
|
# Custom indexing of result set elements
|
|
# Value casting and preparation
|
|
# Value assignment listening
|
|
# Makes multi-dimensional array out of the two-dimensional result set array, the number of dimensions is equal to the number of nested joins
|
|
|
|
Now consider the DQL equivalent of the SQL query we used:
|
|
<code type="php">
|
|
$array = $conn->query('SELECT u.id, u.name, p.phonenumber FROM User u LEFT JOIN u.Phonenumber p',
|
|
array(), Doctrine::HYDRATE_ARRAY);
|
|
</code>
|
|
|
|
The structure of this hydrated array would look like:
|
|
|
|
<code>
|
|
array(0 => array('id' => 1,
|
|
'name' => 'Jack Daniels',
|
|
'Phonenumber' =>
|
|
array(0 => array('phonenumber' => '123 123'),
|
|
1 => array('phonenumber' => '456 456'))),
|
|
1 => array('id' => 2,
|
|
'name' => 'John Beer',
|
|
'Phonenumber' =>
|
|
array(0 => array('phonenumber' => '111 111'))),
|
|
2 => array('id' => 3,
|
|
'name' => 'John Smith',
|
|
'Phonenumber' =>
|
|
array(0 => array('phonenumber' => '111 111')),
|
|
2 => array('phonenumber' => '222 222'),
|
|
3 => array('phonenumber' => '333 333'))));
|
|
</code>
|
|
|
|
This structure also applies to the hydration of objects(records) which is the default hydration mode of Doctrine. The only differences are that the individual elements are represented as Doctrine_Record objects and the arrays converted into Doctrine_Collection objects. Whether dealing with arrays or objects you can:
|
|
|
|
# Iterate over the results using //foreach//
|
|
# Access individual elements using array access brackets
|
|
# Get the number of elements using //count()// function
|
|
# Check if given element exists using //isset()//
|
|
# Unset given element using //unset()//
|
|
|
|
You should always use array hydration when you only need to data for access-only purposes, whereas you should use the record hydration when you need to change the fetched data.
|
|
|
|
The constant O(n) performance of the hydration algorithm is ensured by a smart identifier caching solution.
|
|
|
|
+++ Field lazy-loading
|
|
|
|
Whenever you fetch an object that has not all of its fields loaded from database then the state of this object is called proxy. Proxy objects can load the unloaded fields lazily.
|
|
|
|
Lets say we have a User class with the following definition:
|
|
|
|
<code type="php">
|
|
class User extends Doctrine_Record
|
|
{
|
|
public function setTableDefinition()
|
|
{
|
|
$this->hasColumn('name', 'string', 20);
|
|
$this->hasColumn('password', 'string', 16);
|
|
$this->hasColumn('description', 'string');
|
|
}
|
|
}
|
|
</code>
|
|
|
|
In the following example we fetch all the Users with the fields name and password loaded directly. Then we lazy-load a huge field called description for one user.
|
|
|
|
<code type="php">
|
|
$users = Doctrine_Query::create()->select('u.name, u.password')->from('User u')->execute();
|
|
|
|
// the following lazy-loads the description fields and executes one additional database query
|
|
$users[0]->description;
|
|
</code>
|
|
|
|
Doctrine does the proxy evaluation based on loaded field count. It does not evaluate which fields are loaded on field-by-field basis. The reason for this is simple: performance. Field lazy-loading is very rarely needed in PHP world, hence introducing some kind of variable to check which fields are loaded would introduce unnecessary overhead to basic fetching.
|
|
|
|
++ Arrays and objects
|
|
|
|
Doctrine_Records and Doctrine_Collections provide methods to facilitate working with arrays: {{toArray()}}, {{fromArray()}} and {{synchronizeWithArray()}}.
|
|
|
|
+++ toArray
|
|
|
|
The {{toArray()}} method returns an array representation of your records or collections. It also accesses the relationships the objects may have. If you need to print a record for debugging purposes you can get an array representation of the object and print that.
|
|
|
|
<code type="php">
|
|
print_r ($user->toArray()); // toArray(false) if you don't want to get the relations
|
|
</code>
|
|
|
|
+++ fromArray
|
|
|
|
If you have an array of values you want to use to fill a record or even a collection, the {{fromArray()}} method simplifies this common task.
|
|
|
|
<code type="php">
|
|
// If you have an array like this
|
|
$data = array(
|
|
'name' => 'John',
|
|
'age' => '25',
|
|
'Emails' => array('john@mail.com', 'john@work.com')
|
|
);
|
|
|
|
// you can populate a user record with an Emails relationship like this
|
|
$user = new User();
|
|
$user->fromArray($data);
|
|
$user->Emails->count(); // --> 2
|
|
</code>
|
|
|
|
+++ synchronizeWithArray
|
|
|
|
{{synchronizeWithArray()}} allows you to... well, synchronize a record with an array. So if have an array representation of your model and modify a field, modify a relationship field or even delete or create a relationship, this changes will be applied to the record.
|
|
|
|
<code type="php">
|
|
$user = Doctrine_Query::create()
|
|
->from('User')
|
|
->leftJoin('Groups')
|
|
->where('id = ?')
|
|
->fetchOne(array(1));
|
|
|
|
// Display this object on a cool javascript form that allows you to:
|
|
|
|
$arrayUser['name'] = 'New name'; // modify a field
|
|
$arrayUser['Group'][0]['name'] = 'Renamed Group'; // modify a field on a relation
|
|
$arrayUser['Group'][] = array('name' => 'New Group'); // create a new relation
|
|
unset($arrayUser['Group'][1]); // even remove a relation
|
|
|
|
// submit the form and on the next script use the same query to retrieve the record
|
|
|
|
$user = Doctrine_Query::create()
|
|
->from('User')
|
|
->leftJoin('Groups')
|
|
->where('id = ?')
|
|
->fetchOne(array(1));
|
|
|
|
// sanitize the form input an get the data
|
|
|
|
$user->synchronizeWithArray($arrayUser);
|
|
$user->save(); // all changes get applied to the user object
|
|
</code>
|
|
|
|
++ Overriding the constructor
|
|
|
|
Sometimes you want to do some operations at the creation time of your objects. Doctrine doesn't allow you to override the Doctrine_Record::__construct() method but provides an alternative:
|
|
|
|
<code type="php">
|
|
class User extends Doctrine_Record
|
|
{
|
|
public function construct()
|
|
{
|
|
$this->name = 'Test Name';
|
|
$this->do_something();
|
|
}
|
|
}
|
|
</code>
|
|
|
|
The only drawback is that it doesn't provide a way to pass parameters to the constructor.
|