1
0
mirror of synced 2024-12-15 23:56:02 +03:00

Updated Cookbook Getting Started XML Edition Tutorial with Array and Scalar Hydration examples, tweaked several passages a little bit

This commit is contained in:
beberlei 2010-06-10 21:38:22 +02:00
parent 51729cbaa7
commit d872d7f0b1

View File

@ -17,7 +17,7 @@ that contains the data which is persisted and retrieved by Doctrine's data mappi
## An Example Model: Bug Tracker
For this Getting Started Guide for Doctrine we will implement the Bug Tracker domain model from the [Zend_Db_Table](http://framework.zend.com/manual/en/zend.db.table.html)
documentation. Reading that documentat we can extract the requirements to be:
documentation. Reading their documentation we can extract the requirements to be:
* A Bugs has a description, creation date, status, reporter and engineer
* A bug can occour on different products (platforms)
@ -491,7 +491,7 @@ Having created the schema we can now start and save entities in the database. Fo
$entityManager->persist($user);
$entityManager->flush();
Having a user, he can create products:
Products can also be created:
[php]
$newProductName = "My Product";
@ -509,12 +509,16 @@ You have to explicitly call `flush()` to have the EntityManager write those two
You might wonder why does this distinction between persist notification and flush exist? Doctrine 2 uses the
UnitOfWork pattern to aggregate all writes (INSERT, UDPATE, DELETE) into one single fast transaction, which
is executed when flushing. This way the write-performance is unbelievable fast. Also in more complex scenarios
than those two before you can request updates on many different entities and all flush them at once.
is executed when flush is called. Using this approach the write-performance is significantly faster than
in a scenario where updates are done for each entity in isolation. In more complex scenarios than the
previous two, you are free to request updates on many different entities and all flush them at once.
Doctrine's UnitOfWork even detects entities that have been retrieved from the database and changed when calling
flush, so that you only have to keep track of those entities that are new or to be removed and pass them to
`EntityManager#persist()` and `EntityManager#remove()` respectively.
Doctrine's UnitOfWork detects entities that have changed after retrieval from the database automatically when
the flush operation is called, so that you only have to keep track of those entities that are new or to be removed and pass them to
`EntityManager#persist()` and `EntityManager#remove()` respectively. This comparison to find dirty
entities that need updating is using a very efficient algorithm that has almost no additional
memory overhead and can even save you computing power by only updating those database columns
that really changed.
We are now getting to the "Create a New Bug" requirement and the code for this scenario may look like this:
@ -550,6 +554,8 @@ is called and relate them in the database appropriately.
## Queries for Application Use-Cases
### List of Bugs
Using the previous examples we can fill up the database quite a bit, however we now need to discuss how to query the underlying
mapper for the required view representations. When opening the application, bugs can be paginated through a list-view, which is the first
read-only use-case:
@ -605,6 +611,38 @@ in one single SQL statement. The console output of this script is then:
> Using Native SQL you could even use stored procedures for data retrieval, or make use of advanced non-portable
> database queries like PostgreSql's recursive queries.
### Array Hydration of the Bug List
In the previous use-case we retrieved the result as their respective object instances.
We are not limitied to retrieving objects only from Doctrine however. For a simple list view
like the previous one we only need read access to our entities and can switch the hydration
from objects to simple PHP arrays instead. This can obviously yiel considerable performance benefits for read-only requests.
Implementing the same list view as before using array hydration we can rewrite our code:
[php]
$dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ".
"JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC";
$query = $em->createQuery($dql);
$bugs = $query->getArrayResult();
foreach ($bugs AS $bug) {
echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n";
echo " Reported by: ".$bug['reporter']['name']."\n";
echo " Assigned to: ".$bug['engineer']['name']."\n";
foreach($bug['products'] AS $product) {
echo " Platform: ".$product['name']."\n";
}
echo "\n";
}
There is one significant difference in the DQL query however, we have
to add an additional fetch-join for the products connected to a bug. The resulting
SQL query for this single select statement is pretty large, however still
more efficient to retrieve compared to hydrating objects.
### Find by Primary Key
The next Use-Case is displaying a Bug by primary key. This could be done using DQL as in the previous example with a where clause,
however there is a convenience method on the Entity Manager that handles loading by primary key, which we have already
seen in the write scenarios:
@ -619,8 +657,10 @@ However we will soon see another problem with our entities using this approach.
echo "Engineer: ".$bug->getEngineer()->name."\n";
It will be null! What is happening? It worked in the previous example, so it can't be a problem with the persistance
code of Doctrine. You walked in the public property trap. Since we only retrieved the bug by primary key both the
engineer and reporter are not immediately loaded from the database but are replaced by LazyLoading proxies. Sample
code of Doctrine. What is it then? You walked in the public property trap.
Since we only retrieved the bug by primary key both the engineer and reporter are not immediately loaded
from the database but are replaced by LazyLoading proxies. Sample
code of this proxy generated code can be found in the specified Proxy Directory, it looks like:
[php]
@ -647,8 +687,8 @@ code of this proxy generated code can be found in the specified Proxy Directory,
}
See how upon each method call the proxy is lazily loaded from the database? Using public properties however
we never call a method and Doctrine has no way to hook into the PHP Engine to detect this access and trigger
the lazy load. We need to revise our entities, make all the properties private or protected and add getters
we never call a method and Doctrine has no way to hook into the PHP Engine to detect a direct access to a public property
and trigger the lazy load. We need to rewrite our entities, make all the properties private or protected and add getters
and setters to get a working example:
[php]
@ -660,6 +700,11 @@ and setters to get a working example:
Engineer: beberlei
*/
Being required to use private or protected properties Doctrine 2 actually enforces you to encapsulate
your objects according to object-oriented best-practices.
## Dashboard of the User
For the next use-case we want to retrieve the dashboard view, a list of all open bugs the user reported or
was assigned to. This will be achieved using DQL again, this time with some WHERE clauses and usage of bound parameters:
@ -679,6 +724,23 @@ was assigned to. This will be achieved using DQL again, this time with some WHER
That is it for the read-scenarios of this example, we will continue with the last missing bit, engineers
being able to close a bug.
## Number of Bugs
Until now we only retrieved entities or their array representation. Doctrine also supports the retrieval
of non-entities through DQL. These values are called "scalar result values" and may even be aggregate
values using COUNT, SUM, MIN, MAX or AVG functions.
We will need this knowledge to retrieve the number of open bugs grouped by product:
[php]
$dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ".
"JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id";
$productBugs = $em->createQuery($dql)->getScalarResult();
foreach($productBugs as $productBug) {
echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n";
}
## Updating Entities
There is a single use-case missing from the requirements, Engineers should be able to close a bug. This looks like: