Updated Cookbook Getting Started XML Edition Tutorial with Array and Scalar Hydration examples, tweaked several passages a little bit
This commit is contained in:
parent
51729cbaa7
commit
d872d7f0b1
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user