2010-11-02 00:03:50 +03:00
|
|
|
|
Working with Objects
|
|
|
|
|
====================
|
|
|
|
|
|
2010-11-01 23:16:12 +03:00
|
|
|
|
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 implicitly 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.
|
|
|
|
|
|
2010-11-02 00:03:50 +03:00
|
|
|
|
.. note::
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
It is very important to understand that only
|
|
|
|
|
``EntityManager#flush()`` ever causes write operations against the
|
|
|
|
|
database to be executed. Any other methods such as
|
|
|
|
|
``EntityManager#persist($entity)`` or
|
|
|
|
|
``EntityManager#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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Entities and the Identity Map
|
|
|
|
|
-----------------------------
|
|
|
|
|
|
|
|
|
|
Entities are objects with identity. Their identity has a conceptual
|
|
|
|
|
meaning inside your domain. In a CMS application each article has a
|
|
|
|
|
unique id. You can uniquely identify each article by that id.
|
|
|
|
|
|
|
|
|
|
Take the following example, where you find an article with the
|
|
|
|
|
headline "Hello World" with the ID 1234:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
$article = $entityManager->find('CMS\Article', 1234);
|
|
|
|
|
$article->setHeadline('Hello World dude!');
|
|
|
|
|
|
|
|
|
|
$article2 = $entityManager->find('CMS\Article', 1234);
|
|
|
|
|
echo $article2->getHeadline();
|
|
|
|
|
|
|
|
|
|
In this case the Article is accessed from the entity manager twice,
|
|
|
|
|
but modified in between. Doctrine 2 realizes this and will only
|
|
|
|
|
ever give you access to one instance of the Article with ID 1234,
|
|
|
|
|
no matter how often do you retrieve it from the EntityManager and
|
|
|
|
|
even no matter what kind of Query method you are using (find,
|
|
|
|
|
Repository Finder or DQL). This is called "Identity Map" pattern,
|
|
|
|
|
which means Doctrine keeps a map of each entity and ids that have
|
|
|
|
|
been retrieved per PHP request and keeps returning you the same
|
|
|
|
|
instances.
|
|
|
|
|
|
|
|
|
|
In the previous example the echo prints "Hello World dude!" to the
|
|
|
|
|
screen. You can even verify that ``$article`` and ``$article2`` are
|
|
|
|
|
indeed pointing to the same instance by running the following
|
|
|
|
|
code:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
if ($article === $article2) {
|
|
|
|
|
echo "Yes we are the same!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Sometimes you want to clear the identity map of an EntityManager to
|
|
|
|
|
start over. We use this regularly in our unit-tests to enforce
|
|
|
|
|
loading objects from the database again instead of serving them
|
|
|
|
|
from the identity map. You can call ``EntityManager#clear()`` to
|
|
|
|
|
achieve this result.
|
|
|
|
|
|
|
|
|
|
Entity Object Graph Traversal
|
|
|
|
|
-----------------------------
|
|
|
|
|
|
|
|
|
|
Although Doctrine allows for a complete separation of your domain
|
|
|
|
|
model (Entity classes) there will never be a situation where
|
|
|
|
|
objects are "missing" when traversing associations. You can walk
|
|
|
|
|
all the associations inside your entity models as deep as you
|
|
|
|
|
want.
|
|
|
|
|
|
|
|
|
|
Take the following example of a single ``Article`` entity fetched
|
|
|
|
|
from newly opened EntityManager.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
/** @Entity */
|
|
|
|
|
class Article
|
|
|
|
|
{
|
|
|
|
|
/** @Id @Column(type="integer") @GeneratedValue */
|
|
|
|
|
private $id;
|
|
|
|
|
|
|
|
|
|
/** @Column(type="string") */
|
|
|
|
|
private $headline;
|
|
|
|
|
|
|
|
|
|
/** @ManyToOne(targetEntity="User") */
|
|
|
|
|
private $author;
|
|
|
|
|
|
|
|
|
|
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
|
|
|
|
|
private $comments;
|
|
|
|
|
|
|
|
|
|
public function __construct {
|
|
|
|
|
$this->comments = new ArrayCollection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getAuthor() { return $this->author; }
|
|
|
|
|
public function getComments() { return $this->comments; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$article = $em->find('Article', 1);
|
|
|
|
|
|
2010-12-16 01:12:58 +03:00
|
|
|
|
This code only retrieves the ``Article`` instance with id 1 executing
|
2010-11-01 23:16:12 +03:00
|
|
|
|
a single SELECT statement against the user table in the database.
|
|
|
|
|
You can still access the associated properties author and comments
|
|
|
|
|
and the associated objects they contain.
|
|
|
|
|
|
|
|
|
|
This works by utilizing the lazy loading pattern. Instead of
|
|
|
|
|
passing you back a real Author instance and a collection of
|
|
|
|
|
comments Doctrine will create proxy instances for you. Only if you
|
|
|
|
|
access these proxies for the first time they will go through the
|
|
|
|
|
EntityManager and load their state from the database.
|
|
|
|
|
|
|
|
|
|
This lazy-loading process happens behind the scenes, hidden from
|
|
|
|
|
your code. See the following code:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
$article = $em->find('Article', 1);
|
|
|
|
|
|
|
|
|
|
// accessing a method of the user instance triggers the lazy-load
|
|
|
|
|
echo "Author: " . $article->getAuthor()->getName() . "\n";
|
|
|
|
|
|
|
|
|
|
// Lazy Loading Proxies pass instanceof tests:
|
|
|
|
|
if ($article->getAuthor() instanceof User) {
|
|
|
|
|
// a User Proxy is a generated "UserProxy" class
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// accessing the comments as an iterator triggers the lazy-load
|
|
|
|
|
// retrieving ALL the comments of this article from the database
|
|
|
|
|
// using a single SELECT statement
|
|
|
|
|
foreach ($article->getComments() AS $comment) {
|
|
|
|
|
echo $comment->getText() . "\n\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Article::$comments passes instanceof tests for the Collection interface
|
|
|
|
|
// But it will NOT pass for the ArrayCollection interface
|
|
|
|
|
if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) {
|
|
|
|
|
echo "This will always be true!";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
A slice of the generated proxy classes code looks like the
|
|
|
|
|
following piece of code. A real proxy class override ALL public
|
|
|
|
|
methods along the lines of the ``getName()`` method shown below:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
class UserProxy extends User implements Proxy
|
|
|
|
|
{
|
|
|
|
|
private function _load()
|
|
|
|
|
{
|
|
|
|
|
// lazy loading code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getName()
|
|
|
|
|
{
|
|
|
|
|
$this->_load();
|
|
|
|
|
return parent::getName();
|
|
|
|
|
}
|
|
|
|
|
// .. other public methods of User
|
|
|
|
|
}
|
|
|
|
|
|
2010-11-02 00:03:50 +03:00
|
|
|
|
.. warning::
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
Traversing the object graph for parts that are lazy-loaded will
|
|
|
|
|
easily trigger lots of SQL queries and will perform badly if used
|
|
|
|
|
to heavily. Make sure to use DQL to fetch-join all the parts of the
|
|
|
|
|
object-graph that you need as efficiently as possible.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Persisting entities
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. note::
|
2010-11-02 00:03:50 +03:00
|
|
|
|
|
|
|
|
|
Invoking the ``persist`` method on an entity does NOT
|
2010-11-01 23:16:12 +03:00
|
|
|
|
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 until
|
|
|
|
|
``EntityManager#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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
$user = new User;
|
|
|
|
|
$user->setName('Mr.Right');
|
|
|
|
|
$em->persist($user);
|
|
|
|
|
$em->flush();
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. note::
|
2010-11-02 00:03:50 +03:00
|
|
|
|
|
|
|
|
|
Generated entity identifiers / primary keys are
|
2010-11-01 23:16:12 +03:00
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- If X is a new entity, it becomes managed. The entity X will be
|
|
|
|
|
entered into the database as a result of the flush operation.
|
|
|
|
|
- If X is a preexisting managed entity, it is ignored by the
|
|
|
|
|
persist operation. However, the persist operation is cascaded to
|
|
|
|
|
entities referenced by X, if the relationships from X to these
|
|
|
|
|
other entities are mapped with cascade=PERSIST or cascade=ALL (see
|
|
|
|
|
"Transitive Persistence").
|
|
|
|
|
- If X is a removed entity, it becomes managed.
|
|
|
|
|
- If X is a detached entity, an exception will be thrown on
|
|
|
|
|
flush.
|
|
|
|
|
|
|
|
|
|
Removing entities
|
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. note::
|
2010-11-02 00:03:50 +03:00
|
|
|
|
|
|
|
|
|
Just like ``persist``, invoking ``remove`` on an entity
|
2010-11-01 23:16:12 +03:00
|
|
|
|
does NOT cause an immediate SQL DELETE to be issued on the
|
|
|
|
|
database. The entity will be deleted on the next invocation of
|
2010-12-11 03:01:44 +03:00
|
|
|
|
``EntityManager#flush()`` that involves that entity. This
|
|
|
|
|
means that entities scheduled for removal can still be queried
|
|
|
|
|
for and appear in query and collection results. See
|
|
|
|
|
the section on :ref:`Database and UnitOfWork Out-Of-Sync <workingobjects_database_uow_outofsync>`
|
|
|
|
|
for more information.
|
|
|
|
|
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
$em->remove($user);
|
|
|
|
|
$em->flush();
|
|
|
|
|
|
|
|
|
|
The semantics of the remove operation, applied to an entity X are
|
|
|
|
|
as follows:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- If X is a new entity, it is ignored by the remove operation.
|
|
|
|
|
However, the remove operation is cascaded to entities referenced by
|
|
|
|
|
X, if the relationship from X to these other entities is mapped
|
|
|
|
|
with cascade=REMOVE or cascade=ALL (see "Transitive Persistence").
|
|
|
|
|
- If X is a managed entity, the remove operation causes it to
|
|
|
|
|
become removed. The remove operation is cascaded to entities
|
|
|
|
|
referenced by X, if the relationships from X to these other
|
|
|
|
|
entities is mapped with cascade=REMOVE or cascade=ALL (see
|
|
|
|
|
"Transitive Persistence").
|
|
|
|
|
- If X is a detached entity, an InvalidArgumentException will be
|
|
|
|
|
thrown.
|
|
|
|
|
- If X is a removed entity, it is ignored by the remove operation.
|
|
|
|
|
- A removed entity X will be removed from the database as a result
|
|
|
|
|
of the flush operation.
|
|
|
|
|
|
|
|
|
|
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 existing
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1. If an association is marked as ``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 to``EntityManager#remove()\`.
|
|
|
|
|
In both cases the cascade remove semantics are applied recursively.
|
|
|
|
|
For large object graphs this removal strategy can be very costly.
|
|
|
|
|
2. Using a DQL ``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.
|
|
|
|
|
3. Using foreign key semantics ``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``)
|
|
|
|
|
completely by-passes any foreign key ``onDelete=CASCADE`` option,
|
|
|
|
|
because Doctrine will fetch and remove all associated entities
|
|
|
|
|
explicitly nevertheless.
|
|
|
|
|
|
|
|
|
|
Detaching entities
|
|
|
|
|
------------------
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
$em->detach($entity);
|
|
|
|
|
|
|
|
|
|
The semantics of the detach operation, applied to an entity X are
|
|
|
|
|
as follows:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- If X is a managed entity, the detach operation causes it to
|
|
|
|
|
become detached. The detach operation is cascaded to entities
|
|
|
|
|
referenced by X, if the relationships from X to these other
|
|
|
|
|
entities is mapped with cascade=DETACH or cascade=ALL (see
|
|
|
|
|
"Transitive Persistence"). Entities which previously referenced X
|
|
|
|
|
will continue to reference X.
|
|
|
|
|
- If X is a new or detached entity, it is ignored by the detach
|
|
|
|
|
operation.
|
|
|
|
|
- If X is a removed entity, the detach operation is cascaded to
|
|
|
|
|
entities referenced by X, if the relationships from X to these
|
|
|
|
|
other entities is mapped with cascade=DETACH or cascade=ALL (see
|
|
|
|
|
"Transitive Persistence"). Entities which previously referenced X
|
|
|
|
|
will continue to reference X.
|
|
|
|
|
|
|
|
|
|
There are several situations in which an entity is detached
|
|
|
|
|
automatically without invoking the ``detach`` method:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- When ``EntityManager#clear()`` is invoked, all entities that are
|
|
|
|
|
currently managed by the EntityManager instance become detached.
|
|
|
|
|
- When serializing an entity. The entity retrieved upon subsequent
|
|
|
|
|
unserialization will be detached (This is the case for all entities
|
|
|
|
|
that are serialized and stored in some cache, i.e. when using the
|
|
|
|
|
Query Result Cache).
|
|
|
|
|
|
|
|
|
|
The ``detach`` operation is usually not as frequently needed and
|
|
|
|
|
used as ``persist`` and ``remove``.
|
|
|
|
|
|
|
|
|
|
Merging entities
|
|
|
|
|
----------------
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?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.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
When you want to serialize/unserialize entities you
|
2010-11-01 23:16:12 +03:00
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- If X is a detached entity, the state of X is copied onto a
|
|
|
|
|
pre-existing managed entity instance X' of the same identity.
|
|
|
|
|
- If X is a new entity instance, a new managed copy X' will be
|
|
|
|
|
created and the state of X is copied onto this managed instance.
|
|
|
|
|
- If X is a removed entity instance, an InvalidArgumentException
|
|
|
|
|
will be thrown.
|
|
|
|
|
- If X is a managed entity, it is ignored by the merge operation,
|
|
|
|
|
however, the merge operation is cascaded to entities referenced by
|
|
|
|
|
relationships from X if these relationships have been mapped with
|
|
|
|
|
the cascade element value MERGE or ALL (see "Transitive
|
|
|
|
|
Persistence").
|
|
|
|
|
- For all entities Y referenced by relationships from X having the
|
|
|
|
|
cascade element value MERGE or ALL, Y is merged recursively as Y'.
|
|
|
|
|
For all such Y referenced by X, X' is set to reference Y'. (Note
|
|
|
|
|
that if X is managed then X is the same object as X'.)
|
|
|
|
|
- If X is an entity merged to X', with a reference to another
|
|
|
|
|
entity Y, where cascade=MERGE or cascade=ALL is not specified, then
|
|
|
|
|
navigation of the same association from X' yields a reference to a
|
|
|
|
|
managed object Y' with the same persistent identity as Y.
|
|
|
|
|
|
|
|
|
|
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 don't 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.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. warning::
|
|
|
|
|
|
|
|
|
|
If you need to perform multiple merges of entities that share certain subparts
|
|
|
|
|
of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the
|
|
|
|
|
successive calls to ``EntityManager#merge()``. Otherwise you might end up with
|
|
|
|
|
multiple copies of the "same" object in the database, however with different ids.
|
|
|
|
|
|
2010-11-02 00:03:50 +03:00
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
If you load some detached entities from a cache and you do
|
2010-11-01 23:16:12 +03:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Synchronization with the Database
|
|
|
|
|
---------------------------------
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2010-12-11 03:01:44 +03:00
|
|
|
|
.. _workingobjects_database_uow_outofsync:
|
|
|
|
|
|
|
|
|
|
Effects of Database and UnitOfWork being Out-Of-Sync
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
As soon as you begin to change the state ofentities, call persist or remove the
|
|
|
|
|
contents of the UnitOfWork and the database will drive out of sync. They can
|
2011-02-26 13:51:43 +03:00
|
|
|
|
only be sychronized by calling ``EntityManager#flush()``. This section
|
2010-12-11 03:01:44 +03:00
|
|
|
|
describes the effects of database and UnitOfWork being out of sync.
|
|
|
|
|
|
|
|
|
|
- Entities that are scheduled for removal can still be queried from the database.
|
|
|
|
|
They are returned from DQL and Repository queries and are visible in collections.
|
|
|
|
|
- Entities that are passed to ``EntityManager#persist`` do not turn up in query
|
|
|
|
|
results.
|
|
|
|
|
- Entities that have changed will not be overwritten with the state from the database.
|
|
|
|
|
This is because the identity map will detect the construction of an already existing
|
|
|
|
|
entity and assumes its the most up to date version.
|
|
|
|
|
|
|
|
|
|
``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually.
|
|
|
|
|
|
2010-11-01 23:16:12 +03:00
|
|
|
|
Synchronizing New and Managed Entities
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The flush operation applies to a managed entity with the following
|
|
|
|
|
semantics:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- The entity itself is synchronized to the database using a SQL
|
|
|
|
|
UPDATE statement, only if at least one persistent field has
|
|
|
|
|
changed.
|
|
|
|
|
- No SQL updates are executed if the entity did not change.
|
|
|
|
|
|
|
|
|
|
The flush operation applies to a new entity with the following
|
|
|
|
|
semantics:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- The entity itself is synchronized to the database using a SQL
|
|
|
|
|
INSERT statement.
|
|
|
|
|
|
|
|
|
|
For all (initialized) relationships of the new or managed entity
|
|
|
|
|
the following semantics apply to each associated entity X:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- If X is new and persist operations are configured to cascade on
|
|
|
|
|
the relationship, X will be persisted.
|
|
|
|
|
- If X is new and no persist operations are configured to cascade
|
|
|
|
|
on the relationship, an exception will be thrown as this indicates
|
|
|
|
|
a programming error.
|
|
|
|
|
- If X is removed and persist operations are configured to cascade
|
|
|
|
|
on the relationship, an exception will be thrown as this indicates
|
|
|
|
|
a programming error (X would be re-persisted by the cascade).
|
|
|
|
|
- If X is detached and persist operations are configured to
|
|
|
|
|
cascade on the relationship, an exception will be thrown (This is
|
|
|
|
|
semantically the same as passing X to persist()).
|
|
|
|
|
|
|
|
|
|
Synchronizing Removed Entities
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
The size of a Unit of Work mainly refers to the number of managed
|
|
|
|
|
entities at a particular point in time.
|
|
|
|
|
|
|
|
|
|
The cost of flushing
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
How costly a flush operation is, mainly depends on two factors:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- The size of the EntityManager's current UnitOfWork.
|
|
|
|
|
- The configured change tracking policies
|
|
|
|
|
|
|
|
|
|
You can get the size of a UnitOfWork as follows:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?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.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. note::
|
2010-11-02 00:03:50 +03:00
|
|
|
|
|
|
|
|
|
Do not invoke ``flush`` after every change to an entity
|
2010-11-01 23:16:12 +03:00
|
|
|
|
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 call ``flush`` when you are done. While serving a
|
|
|
|
|
single HTTP request there should be usually no need for invoking
|
|
|
|
|
``flush`` more than 0-2 times.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Direct access to a Unit of Work
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
$uow = $em->getUnitOfWork();
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
Directly manipulating a UnitOfWork is not recommended.
|
2010-11-01 23:16:12 +03:00
|
|
|
|
When working directly with the UnitOfWork API, respect methods
|
|
|
|
|
marked as INTERNAL by not using them and carefully read the API
|
|
|
|
|
documentation.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Entity State
|
|
|
|
|
~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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``:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
switch ($em->getUnitOfWork()->getEntityState($entity)) {
|
2011-06-02 07:14:46 +04:00
|
|
|
|
case UnitOfWork::STATE_MANAGED:
|
2010-11-01 23:16:12 +03:00
|
|
|
|
...
|
2011-06-02 07:14:46 +04:00
|
|
|
|
case UnitOfWork::STATE_REMOVED:
|
2010-11-01 23:16:12 +03:00
|
|
|
|
...
|
2011-06-02 07:14:46 +04:00
|
|
|
|
case UnitOfWork::STATE_DETACHED:
|
2010-11-01 23:16:12 +03:00
|
|
|
|
...
|
2011-06-02 07:14:46 +04:00
|
|
|
|
case UnitOfWork::STATE_NEW:
|
2010-11-01 23:16:12 +03:00
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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).
|
|
|
|
|
|
|
|
|
|
Querying
|
|
|
|
|
--------
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
By Primary Key
|
|
|
|
|
~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
// $em instanceof EntityManager
|
|
|
|
|
$user = $em->getRepository('MyProject\Domain\User')->find($id);
|
|
|
|
|
|
|
|
|
|
``EntityManager#getRepository($entityName)`` returns a repository
|
|
|
|
|
object which provides many ways to retrieve 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.
|
|
|
|
|
|
|
|
|
|
By Simple Conditions
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?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');
|
|
|
|
|
|
|
|
|
|
By Eager Loading
|
|
|
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
By Lazy Loading
|
|
|
|
|
~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
By DQL
|
|
|
|
|
~~~~~~
|
|
|
|
|
|
|
|
|
|
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 familiar 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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?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 class can be found in
|
2010-12-03 22:13:10 +03:00
|
|
|
|
:doc:`the dedicated chapter <dql-doctrine-query-language>`.
|
2010-11-01 23:16:12 +03:00
|
|
|
|
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
|
2010-12-03 22:13:10 +03:00
|
|
|
|
:doc:`in Query Builder chapter <query-builder>`.
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
By Native Queries
|
|
|
|
|
~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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 <http://www.doctrine-project.org/documentation/manual/2_0/en/native-sql>`_.
|
|
|
|
|
|
|
|
|
|
Custom Repositories
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?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:
|
|
|
|
|
|
2010-12-03 22:13:10 +03:00
|
|
|
|
.. code-block:: php
|
2010-11-01 23:16:12 +03:00
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
// $em instanceof EntityManager
|
|
|
|
|
|
|
|
|
|
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
|
|
|
|
|
|
|
|
|
|
|