Merge branch 'master' of github.com:doctrine/orm-documentation
This commit is contained in:
commit
53ec0700bb
@ -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:
|
||||
|
@ -267,16 +267,167 @@ Although you get passed the EntityManager in all of these events, you have to fo
|
||||
carefully since operations in the wrong event may produce lots of different errors, such as inconsistent data and
|
||||
lost updates/persists/removes.
|
||||
|
||||
For the described events that are also lifecycle callback events the restrictions
|
||||
apply aswell, with the additional restriction that you do not have access to the EntityManager
|
||||
or UnitOfWork APIs inside these events.
|
||||
|
||||
+++ prePersist
|
||||
|
||||
There are two ways for the `prePersist` event to be triggered. One is obviously
|
||||
when you call `EntityManager#persist()`. The event is also called for all
|
||||
cascaded associations.
|
||||
|
||||
There is another way for `prePersist` to be called, inside the `flush()`
|
||||
method when changes to associations are computed and this association
|
||||
is marked as cascade persist. Any new entity found during this operation
|
||||
is also persisted and `prePersist` called on it. This is called "persistence by reachability".
|
||||
|
||||
In both cases you get passed a `LifecycleEventArgs`
|
||||
instance which has access to the entity and the entity manager.
|
||||
|
||||
The following restrictions apply to `prePersist`:
|
||||
|
||||
* If you are using a PrePersist Identity Generator such as sequences the ID value
|
||||
will *NOT* be available within any PrePersist events.
|
||||
* Doctrine will not recognize changes made to relations in a pre persist event
|
||||
called by "reachibility" through a cascade persist unless you use the internal
|
||||
`UnitOfWork` API. We do not recommend such operations in the persistence by
|
||||
reachability context, so do this at your own risk and possibly supported by unit-tests.
|
||||
|
||||
+++ preRemove
|
||||
|
||||
The `preRemove` event is called on every entity when its passed to
|
||||
the `EntityManager#remove()` method. It is cascaded for all
|
||||
associations that are marked as cascade delete.
|
||||
|
||||
There are no restrictions to what methods can be called inside
|
||||
the `preRemove` event, except when the remove method itself was
|
||||
called during a flush operation.
|
||||
|
||||
+++ onFlush
|
||||
|
||||
OnFlush is a very powerful event. It is called inside `EntityManager#flush()`
|
||||
after the changes to all the managed entities and their associations have
|
||||
been computed. This means, the `onFlush` event has access to the sets of:
|
||||
|
||||
* Entities scheduled for insert
|
||||
* Entities scheduled for update
|
||||
* Entities scheduled for removal
|
||||
* Collections scheduled for update
|
||||
* Collections scheduled for removal
|
||||
|
||||
To make use of the onFlush event you have to be familiar with interal UnitOfWork API,
|
||||
which grants you access to the previously mentioned sets. See this example:
|
||||
|
||||
[php]
|
||||
class FlushExampleListener
|
||||
{
|
||||
public function onFlush(OnFlushEventArgs $eventArgs)
|
||||
{
|
||||
$em = $eventArgs->getEntityManager();
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
foreach ($uow->getScheduledEntityInsertions() AS $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityUpdates() AS $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledEntityDeletions() AS $entity) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionDeletions() AS $col) {
|
||||
|
||||
}
|
||||
|
||||
foreach ($uow->getScheduledCollectionUpdates() AS $col) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The following restrictions apply to the onFlush event:
|
||||
|
||||
* Calling `EntityManager#persist()` does not suffice to trigger a persist on an entity.
|
||||
You have to execute an additional call to `$unitOfWork->computeChangeSet($classMetadata, $entity)`.
|
||||
* Changing primitive fields or associations requires you to explicitly trigger
|
||||
a re-computation of the changeset of the affected entity. This can be done
|
||||
by either calling `$unitOfWork->computeChangeSet($classMetadata, $entity)`
|
||||
or `$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)`. The second
|
||||
method has lower overhead, but only re-computes primitive fields, never associations.
|
||||
|
||||
+++ preUpdate
|
||||
|
||||
PreUpdate is the most restrictive to use event, since it is called right before
|
||||
an update statement is called for an entity inside the `EntityManager#flush()`
|
||||
method.
|
||||
|
||||
Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to
|
||||
correctly handle referential integrity at this point of the flush operation. This
|
||||
event has a powerful feature however, it is executed with a `PreUpdateEventArgs`
|
||||
instance, which contains a reference to the computed change-set of this entity.
|
||||
|
||||
This means you have access to all the fields that have changed for this entity
|
||||
with their old and new value. The following methods are available on the `PreUpdateEventArgs`:
|
||||
|
||||
* `getEntity()` to get access to the actual entity.
|
||||
* `getEntityChangeSet()` to get a copy of the changeset array. Changes to this returned array do not affect updating.
|
||||
* `hasChangedField($fieldName)` to check if the given field name of the current entity changed.
|
||||
* `getOldValue($fieldName)` and `getNewValue($fieldName)` to access the values of a field.
|
||||
* `setNewValue($fieldName, $value)` to change the value of a field to be updated.
|
||||
|
||||
A simple example for this event looks like:
|
||||
|
||||
[php]
|
||||
class NeverAliceOnlyBobListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
{
|
||||
if ($eventArgs->getEntity() instanceof User) {
|
||||
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue() == 'Alice') {
|
||||
$eventArgs->setNewValue('name', 'Bob');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
You could also use this listener to implement validation of all the fields that have changed.
|
||||
This is more efficient than using a lifecycle callback when there are expensive validations
|
||||
to call:
|
||||
|
||||
[php]
|
||||
class ValidCreditCardListener
|
||||
{
|
||||
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||
{
|
||||
if ($eventArgs->getEntity() instanceof Account) {
|
||||
if ($eventArgs->hasChangedField('creditCard')) {
|
||||
$this->validateCreditCard($eventArgs->getNewValue('creditCard'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function validateCreditCard($no)
|
||||
{
|
||||
// throw an exception to interupt flush event. Transaction will be rolled back.
|
||||
}
|
||||
}
|
||||
|
||||
Restrictions for this event:
|
||||
|
||||
* Changes to associations of the passed entities are not recognized by the flush operation anymore.
|
||||
* Changes to fields of the passed entities are not recognized by the flush operation anymore, use the computed change-set passed to the event to modify primitive field values.
|
||||
* Any calls to `EntityManager#persist()` or `EntityManager#remove()`, even in combination with the UnitOfWork API are strongly discouraged and don't work as expected outside the flush operation.
|
||||
|
||||
+++ postUpdate, postRemove, postPersist
|
||||
|
||||
The three post events are called inside `EntityManager#flush()`. Changes in here
|
||||
are not relevant to the persistence in the database, but you can use this events
|
||||
to
|
||||
|
||||
++ Load ClassMetadata Event
|
||||
|
||||
When the mapping information for an entity is read, it is populated in to a
|
||||
|
Loading…
x
Reference in New Issue
Block a user