In this chapter we will help you understand the EntityManager and the UnitOfWork.
A Unit of Work is similar to an object-level transaction. A new Unit of Work is
implicity started when an EntityManager is initially created or after
EntityManager#flush() has been invoked. A Unit of Work is committed
(and a new one started) by invoking EntityManager#flush().
A Unit of Work can be manually closed by calling EntityManager#close(). Any changes to objects within this Unit of Work that have not yet been persisted are lost.
It is very important to understand that only
EntityManager#flush()ever causes write operations against the database to be executed. Any other methods such asEntityManager#persist($entity)orEntityManager#remove($entity)only notify the UnitOfWork to perform these operations during flush.Not calling
EntityManager#flush()will lead to all changes during that request being lost.
An entity can be made persistent by passing it to the EntityManager#persist($entity)
method. By applying the persist operation on some entity, that entity becomes MANAGED,
which means that its persistence is from now on managed by an EntityManager. As a
result the persistent state of such an entity will subsequently be properly
synchronized with the database when EntityManager#flush() is invoked.
Invoking the
persistmethod on an entity does NOT cause an immediate SQL INSERT to be issued on the database. Doctrine applies a strategy called "transactional write-behind", which means that it will delay most SQL commands untilEntityManager#flush()is invoked which will then issue all necessary SQL statements to synchronize your objects with the database in the most efficient way and a single, short transaction, taking care of maintaining referential integrity.
Example:
<?php $user = new User; $user->setName('Mr.Right'); $em->persist($user); $em->flush();
Generated entity identifiers / primary keys are guaranteed to be available after the next successful flush operation that involves the entity in question. You can not rely on a generated identifier to be available directly after invoking
persist. The inverse is also true. You can not rely on a generated identifier being not available after a failed flush operation.
The semantics of the persist operation, applied on an entity X, are as follows:
An entity can be removed from persistent storage by passing it to the EntityManager#remove($entity) method. By applying the remove operation on some entity, that entity becomes REMOVED, which means that its persistent state will be deleted once EntityManager#flush() is invoked.
Just like
persist, invokingremoveon an entity does NOT cause an immediate SQL DELETE to be issued on the database. The entity will be deleted on the next invocation ofEntityManager#flush()that involves that entity.
Example:
<?php $em->remove($user); $em->flush();
The semantics of the remove operation, applied to an entity X are as follows:
After an entity has been removed its in-memory state is the same as before the removal, except for generated identifiers.
Removing an entity will also automatically delete any exisiting records in many-to-many
join tables that link this entity. The action taken depends on the value of the @joinColumn
mapping attribute "onDelete". Either Doctrine issues a dedicated DELETE statement
for records of each join table or it depends on the foreign key semantics of
onDelete="CASCADE".
Deleting an object with all its associated objects can be achieved in multiple ways with very different performance impacts.
CASCADE=REMOVE Doctrine 2 will fetch this
association. If its a Single association it will pass this entity to
´EntityManager#remove(). If the association is a collection, Doctrine will loop over all
its elements and pass them toEntityManager#remove()`. In both cases the
cascade remove semantics are applied recursively. For large object graphs
this removal strategy can be very costly.DELETE statement allows you to delete multiple entities of a
type with a single command and without hydrating these entities. This
can be very efficient to delete large object graphs from the database.onDelete="CASCADE" can force the database
to remove all associated objects internally. This strategy is a bit
tricky to get right but can be very powerful and fast. You should be aware
however that using strategy 1 (CASCADE=REMOVE) completly by-passes
any foreign key onDelete=CASCADE option, because Doctrine will fetch and remove
all associated entities explicitly nevertheless.An entity is detached from an EntityManager and thus no longer managed by
invoking the EntityManager#detach($entity) method on it or by cascading
the detach operation to it. Changes made to the detached entity, if any
(including removal of the entity), will not be synchronized to the database
after the entity has been detached.
Doctrine will not hold on to any references to a detached entity.
Example:
<?php $em->detach($entity);
The semantics of the detach operation, applied to an entity X are as follows:
There are several situations in which an entity is detached automatically without invoking the detach method:
EntityManager#clear() is invoked, all entities that are currently managed by the EntityManager instance become detached.The detach operation is usually not as frequently needed and used as persist and remove.
Merging entities refers to the merging of (usually detached) entities into the
context of an EntityManager so that they become managed again. To merge the
state of an entity into an EntityManager use the EntityManager#merge($entity)
method. The state of the passed entity will be merged into a managed copy of
this entity and this copy will subsequently be returned.
Example:
<?php $detachedEntity = unserialize($serializedEntity); // some detached entity $entity = $em->merge($detachedEntity); // $entity now refers to the fully managed copy returned by the merge operation. // The EntityManager $em now manages the persistence of $entity as usual.
When you want to serialize/unserialize entities you have to make all entity properties protected, never private. The reason for this is, if you serialize a class that was a proxy instance before, the private variables won't be serialized and a PHP Notice is thrown.
The semantics of the merge operation, applied to an entity X, are as follows:
The merge operation will throw an OptimisticLockException if the entity
being merged uses optimistic locking through a version field and the versions
of the entity being merged and the managed copy dont match. This usually means
that the entity has been modified while being detached.
The merge operation is usually not as frequently needed and used as persist
and remove. The most common scenario for the merge operation is to reattach
entities to an EntityManager that come from some cache (and are therefore detached)
and you want to modify and persist such an entity.
If you load some detached entities from a cache and you do not need to persist or delete them or otherwise make use of them without the need for persistence services there is no need to use
merge. I.e. you can simply pass detached objects from a cache directly to the view.
The state of persistent entities is synchronized with the database on flush of an EntityManager
which commits the underlying UnitOfWork. The synchronization involves writing any updates to
persistent entities and their relationships to the database. Thereby bidirectional relationships
are persisted based on the references held by the owning side of the relationship as explained
in the Association Mapping chapter.
When EntityManager#flush() is called, Doctrine inspects all managed, new and removed entities
and will perform the following operations.
The flush operation applies to a managed entity with the following semantics:
The flush operation applies to a new entity with the following semantics:
For all (initialized) relationships of the new or managed entity the following semantics apply to each associated entity X:
The flush operation applies to a removed entity by deleting its persistent state from the database.
No cascade options are relevant for removed entities on flush, the cascade remove option is already
executed during EntityManager#remove($entity).
The size of a Unit of Work mainly refers to the number of managed entities at a particular point in time.
How costly a flush operation is, mainly depends on two factors:
You can get the size of a UnitOfWork as follows:
<?php $uowSize = $em->getUnitOfWork()->size();
The size represents the number of managed entities in the Unit of Work. This size affects the performance of flush() operations due to change tracking (see "Change Tracking Policies") and, of course, memory consumption, so you may want to check it from time to time during development.
Do not invoke
flushafter every change to an entity or every single invocation of persist/remove/merge/... This is an anti-pattern and unnecessarily reduces the performance of your application. Instead, form units of work that operate on your objects and callflushwhen you are done. While serving a single HTTP request there should be usually no need for invokingflushmore than 0-2 times.
You can get direct access to the Unit of Work by calling EntityManager#getUnitOfWork().
This will return the UnitOfWork instance the EntityManager is currently using.
<?php $uow = $em->getUnitOfWork();
Directly manipulating a UnitOfWork is not recommended. When working directly with the UnitOfWork API, respect methods marked as INTERNAL by not using them and carefully read the API documentation.
As outlined in the architecture overview an entity can be in one of four possible states:
NEW, MANAGED, REMOVED, DETACHED. If you explicitly need to find out what the current state
of an entity is in the context of a certain EntityManager you can ask the underlying
UnitOfWork:
<?php switch ($em->getUnitOfWork()->getEntityState($entity)) { case UnitOfWork::MANAGED: ... case UnitOfWork::REMOVED: ... case UnitOfWork::DETACHED: ... case UnitOfWork::NEW: ... }
An entity is in MANAGED state if it is associated with an EntityManager and it is not REMOVED.
An entity is in REMOVED state after it has been passed to EntityManager#remove() until the
next flush operation of the same EntityManager. A REMOVED entity is still associated with an
EntityManager until the next flush operation.
An entity is in DETACHED state if it has persistent state and identity but is currently not
associated with an EntityManager.
An entity is in NEW state if has no persistent state and identity and is not associated with an
EntityManager (for example those just created via the "new" operator).
Doctrine 2 provides the following ways, in increasing level of power and flexibility, to query for persistent objects. You should always start with the simplest one that suits your needs.
The most basic way to query for a persistent object is by its identifier / primary key using the EntityManager#find($entityName, $id) method. Here is an example:
<?php // $em instanceof EntityManager $user = $em->find('MyProject\Domain\User', $id);
The return value is either the found entity instance or null if no instance could be found with the given identifier.
Essentially, EntityManager#find() is just a shortcut for the following:
<?php // $em instanceof EntityManager $user = $em->getRepository('MyProject\Domain\User')->find($id);
EntityManager#getRepository($entityName) returns a repository object which provides many ways to retreive entities of the specified type. By default, the repository instance is of type Doctrine\ORM\EntityRepository. You can also use custom repository classes as shown later.
To query for one or more entities based on several conditions that form a logical conjunction, use the findBy and findOneBy methods on a repository as follows:
<?php // $em instanceof EntityManager // All users that are 20 years old $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); // All users that are 20 years old and have a surname of 'Miller' $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); // A single user by its nickname $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb'));
An EntityRepository also provides a mechanism for more concise calls through its use of __call. Thus, the following two examples are equivalent:
<?php // A single user by its nickname $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); // A single user by its nickname (__call magic) $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb');
Whenever you query for an entity that has persistent associations and these associations are mapped as EAGER, they will automatically be loaded together with the entity being queried and is thus immediately available to your application.
Whenever you have a managed entity instance at hand, you can traverse and use any associations of that entity that are configured LAZY as if they were in-memory already. Doctrine will automatically load the associated objects on demand through the concept of lazy-loading.
The most powerful and flexible method to query for persistent objects is the Doctrine Query Language, an object query language. DQL enables you to query for persistent objects in the language of objects. DQL understands classes, fields, inheritance and associations. DQL is syntactically very similar to the familar SQL but it is not SQL.
A DQL query is represented by an instance of the Doctrine\ORM\Query class. You create a query using EntityManager#createQuery($dql). Here is a simple example:
<?php // $em instanceof EntityManager // All users with an age between 20 and 30 (inclusive). $q = $em->createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); $users = $q->getResult();
Note that this query contains no knowledge about the relational schema, only about the object model. DQL supports positional as well as named parameters, many functions, (fetch) joins, aggregates, subqueries and much more. Detailed information about DQL and its syntax as well as the Doctrine\ORM\Query class can be found in the dedicated chapter. For programmatically building up queries based on conditions that are only known at runtime, Doctrine provides the special Doctrine\ORM\QueryBuilder class. More information on constructing queries with a QueryBuilder can be found in the dedicated chapter.
As an alternative to DQL or as a fallback for special SQL statements native queries can be used. Native queries are built by using a hand-crafted SQL query and a ResultSetMapping that describes how the SQL result set should be transformed by Doctrine. More information about native queries can be found in the dedicated chapter.
By default the EntityManager returns a default implementation of Doctrine\ORM\EntityRepository when
you call EntityManager#getRepository($entityClass). You can overwrite this behaviour by specifying
the class name of your own Entity Repository in the Annotation, XML or YAML metadata.
In large applications that require lots of specialized DQL queries using a custom repository is
one recommended way of grouping these queries in a central location.
<?php namespace MyDomain\Model; use Doctrine\ORM\EntityRepository; /** * @entity(repositoryClass="MyDomain\Model\UserRepository") */ class User { } class UserRepository extends EntityRepository { public function getAllAdminUsers() { return $this->_em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') ->getResult(); } }
You can access your repository now by calling:
<?php // $em instanceof EntityManager $admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();