7. Working with Objects¶
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.
Note
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.
Note
Doctrine NEVER touches the public API of methods in your entity classes (like getters and setters) nor the constructor method. Instead, it uses reflection to get/set data from/to your entity objects. When Doctrine fetches data from DB and saves it back, any code put in your get/set methods won’t be implicitly taken into account.
7.1. 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:
<?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 ORM 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:
<?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.
7.2. 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.
<?php
#[Entity]
class Article
{
#[Id, Column(type: 'integer'), GeneratedValue]
private int|null $id = null;
#[Column(type: 'string')]
private string $headline;
#[ManyToOne(targetEntity: User::class)]
private User|null $author = null;
/** @var Collection<int, Comment> */
#[OneToMany(targetEntity: Comment::class, mappedBy: 'article')]
private Collection $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getAuthor(): User|null { return $this->author; }
public function getComments(): Collection { return $this->comments; }
}
$article = $em->find('Article', 1);
This code only retrieves the Article
instance with id 1 executing
a single SELECT statement against the articles 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:
<?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!";
}
Warning
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.
7.3. 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.
Note
Invoking the persist
method 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 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:
<?php
$user = new User;
$user->setName('Mr.Right');
$em->persist($user);
$em->flush();
Note
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:
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.
X が新しいエンティティの場合は、管理されます。エンティティ X は、フラッシュ操作の結果としてデータベースに書き込まれます。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 / Cascade Operations”).
X が既存の管理対象エンティティである場合、persist 操作では無視されます。ただし、X からこれらの他のエンティティーへの関係が cascade=PERSIST または cascade=ALL でマップされている場合、持続操作は X によって参照されるエンティティーにカスケードされます (「推移的持続性 / カスケード操作」を参照)。If X is a removed entity, it becomes managed.
X が削除されたエンティティの場合、管理されます。If X is a detached entity, an exception will be thrown on flush.
X がデタッチされたエンティティの場合、例外が onflush にスローされます。
Caution
Do not pass detached entities to the persist operation. The persist operation always
considers entities that are not yet known to the EntityManager
as new entities
(refer to the STATE_NEW
constant inside the UnitOfWork
).
7.4. 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.
Note
Just like persist
, invoking remove
on an entity
does NOT cause an immediate SQL DELETE to be issued on the
database. The entity will be deleted on the next invocation of
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 Database and UnitOfWork Out-Of-Sync
for more information.
Example:
<?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 / Cascade Operations”).
X が新しいエンティティである場合、削除操作では無視されます。ただし、X からこれらの他のエンティティへの関係が cascade=REMOVE または cascade=ALL でマップされている場合、削除操作は X によって参照されるエンティティにカスケードされます (「推移的な永続性」を参照)。 /カスケード操作」)。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 / Cascade Operations”).
X が管理対象エンティティの場合、削除操作によって X は削除されます。 X からこれらの他のエンティティーへの関係が cascade=REMOVE または cascade=ALL でマップされている場合、削除操作は X によって参照されるエンティティーにカスケードされます (「推移的永続性 / カスケード操作」を参照)。If X is a detached entity, an InvalidArgumentException will be thrown.
X が切り離されたエンティティの場合、InvalidArgumentException がスローされます。If X is a removed entity, it is ignored by the remove operation.
X が削除されたエンティティの場合、削除操作では無視されます。A removed entity X will be removed from the database as a result of the flush operation.
削除されたエンティティ X は、フラッシュ操作の結果としてデータベースから削除されます。
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.
If an association is marked as
CASCADE=REMOVE
Doctrine ORM will fetch this association. If its a Single association it will pass this entity toEntityManager#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.アソシエーションが CASCADE=REMOVE としてマークされている場合、Doctrine ORM はこのアソシエーションを取得します。単一の関連付けの場合、このエンティティを EntityManager#remove() に渡します。関連付けがコレクションの場合、Doctrine はすべての要素をループして EntityManager#remove() に渡します。どちらの場合も、カスケード削除セマンティクスが再帰的に適用されます。大きなオブジェクト グラフの場合、この削除戦略は非常にコストがかかる可能性があります。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.DQL DELETE ステートメントを使用すると、これらのエンティティをハイドレートすることなく、1 つのコマンドでタイプの複数のエンティティを削除できます。これは、データベースから大きなオブジェクトグラフを削除するのに非常に効率的です。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 keyonDelete=CASCADE
option, because Doctrine will fetch and remove all associated entities explicitly nevertheless.外部キー セマンティクス onDelete="CASCADE" を使用すると、データベースが関連するすべてのオブジェクトを内部的に削除するように強制できます。この戦略を正しく理解するのは少し難しいですが、非常に強力で高速です。ただし、戦略 1 (CASCADE=REMOVE) を使用すると、外部キーの onDelete=CASCADE オプションが完全にバイパスされることに注意してください。Doctrine は関連するすべてのエンティティを明示的に取得して削除するためです。
Note
Calling remove
on an entity will remove the object from the identity
map and therefore detach it. Querying the same entity again, for example
via a lazy loaded relation, will return a new object.
7.5. 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:
<?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 / Cascade Operations”). Entities which previously referenced X will continue to reference X.
X が管理対象エンティティの場合、デタッチ操作により X はデタッチされます。 X からこれらの他のエンティティーへの関係が cascade=DETACH または cascade=ALL でマップされている場合、デタッチ操作は X によって参照されるエンティティーにカスケードされます (「推移的永続性 / カスケード操作」を参照)。以前に X を参照していたエンティティは、引き続き X を参照します。If X is a new or detached entity, it is ignored by the detach operation.
X が新しいエンティティまたは切り離されたエンティティである場合、切り離し操作では無視されます。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 / Cascade Operations”). Entities which previously referenced X will continue to reference X.
X が削除されたエンティティである場合、X からこれらの他のエンティティへの関係が cascade=DETACH または cascade=ALL でマップされている場合、デタッチ操作は X によって参照されるエンティティにカスケードされます (「Transitive persistence / Cascade Operations」を参照)。以前に X を参照していたエンティティは、引き続き 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.EntityManager#clear() が呼び出されると、現在 EntityManager インスタンスによって管理されているすべてのエンティティが切り離されます。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).
エンティティをシリアル化するとき。その後の非シリアル化で取得されたエンティティは切り離されます (これは、シリアル化されてキャッシュに格納されたすべてのエンティティに当てはまります)。
The detach
operation is usually not as frequently needed and
used as persist
and remove
.
7.6. 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:
<?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.
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.
X が切り離されたエンティティである場合、X の状態は、同じ ID の既存の管理対象エンティティ インスタンス X' にコピーされます。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.
X が新しいエンティティ インスタンスの場合、新しいマネージド コピー X' が作成され、X の状態がこのマネージド インスタンスにコピーされます。If X is a removed entity instance, an InvalidArgumentException will be thrown.
X が削除されたエンティティ インスタンスである場合、InvalidArgumentException がスローされます。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 / Cascade Operations”).
X が管理対象エンティティである場合、マージ操作では無視されますが、これらの関係がカスケード要素値 MERGE または ALL でマップされている場合、マージ操作は X からの関係によって参照されるエンティティにカスケードされます (「推移的永続性 / カスケード操作」を参照)。 )。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’.)
カスケード要素値 MERGE または ALL を持つ X からの関係によって参照されるすべてのエンティティ Y について、Y は Y’ として再帰的にマージされます。X によって参照されるすべての Y について、X’ は参照 Y’ に設定されます。 (X が管理されている場合、X は 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.
X が X' にマージされたエンティティであり、cascade=MERGE または cascade=ALL が指定されていない別のエンティティ Y への参照がある場合、X' からの同じ関連付けのナビゲートにより、同じ永続 ID を持つ管理対象オブジェクト Y' への参照が生成されます。 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.
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.
Note
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.
7.7. 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.
7.7.1. Effects of Database and UnitOfWork being Out-Of-Sync¶
As soon as you begin to change the state of entities, call persist or remove the
contents of the UnitOfWork and the database will drive out of sync. They can
only be synchronized by calling EntityManager#flush()
. This section
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.
削除がスケジュールされているエンティティは、引き続きデータベースからクエリできます。エンティティは、DQL およびリポジトリ クエリから返され、コレクションに表示されます。Entities that are passed to
EntityManager#persist
do not turn up in query results.EntityManager#persist に渡されたエンティティは、クエリ結果に表示されません。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.
変更されたエンティティは、データベースからの状態で上書きされません。これは、ID マップが既存のエンティティの構築を検出し、最新バージョンであると想定するためです。
EntityManager#flush()
is never called implicitly by Doctrine. You always have to trigger it manually.
7.7.2. 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.
エンティティ自体は、少なくとも 1 つの永続フィールドが変更された場合にのみ、SQLUPDATE ステートメントを使用してデータベースに同期されます。No SQL updates are executed if the entity did not change.
エンティティが変更されていない場合、SQL 更新は実行されません。
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.
エンティティ自体は、SQLINSERT ステートメントを使用してデータベースに同期されます。
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.
X が新しく、永続化操作が関係にカスケードするように構成されている場合、X は永続化されます。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.
X が新しく、永続操作が関係をカスケードするように構成されていない場合、これはプログラミング エラーを示すため、例外がスローされます。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).
X が削除され、保持操作が関係をカスケードするように構成されている場合、これはプログラミング エラーを示すため、例外がスローされます (X はカスケードによって再保持されます)。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()).
X がデタッチされ、永続操作がリレーションシップでカスケードするように構成されている場合、例外がスローされます (これは、X を persist() に渡すことと本質的に同じです)。
7.7.3. 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)
.
7.7.4. 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.
7.7.5. The cost of flushing¶
How costly a flush operation is, mainly depends on two factors:
The size of the EntityManager’s current UnitOfWork.
EntityManager の現在の UnitOfWork のサイズ。The configured change tracking policies
構成された変更追跡ポリシー
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.
Note
Do not invoke flush
after 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 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.
7.7.6. 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.
<?php
$uow = $em->getUnitOfWork();
Note
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.
7.7.7. 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
:
<?php
switch ($em->getUnitOfWork()->getEntityState($entity)) {
case UnitOfWork::STATE_MANAGED:
...
case UnitOfWork::STATE_REMOVED:
...
case UnitOfWork::STATE_DETACHED:
...
case UnitOfWork::STATE_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).
7.8. Querying¶
Doctrine ORM 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.
7.8.1. 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:
<?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 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.
7.8.2. 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:
<?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'));
You can also load by owning side associations through the repository:
<?php
$number = $em->find('MyProject\Domain\Phonenumber', 1234);
$user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId()));
The EntityRepository#findBy()
method additionally accepts orderings, limit and offset as second to fourth parameters:
<?php
$tenUsers = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0);
If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically:
<?php
$users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40)));
// translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40)
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');
Additionally, you can just count the result of the provided conditions when you don’t really need the data:
<?php
// Check there is no user with nickname
$availableNickname = 0 === $em->getRepository('MyProject\Domain\User')->count(['nickname' => 'nonexistent']);
7.8.3. By Criteria¶
The Repository implement the Doctrine\Common\Collections\Selectable
interface. That means you can build Doctrine\Common\Collections\Criteria
and pass them to the matching($criteria)
method.
See section Filtering collections of chapter Working with Associations
7.8.4. 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.
7.8.5. 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.
7.8.6. 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:
<?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
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. While this a powerful tool,
it also brings more complexity to your code compared to plain DQL,
so you should only use it when you need it. More information on
constructing queries with a QueryBuilder can be found
in Query Builder chapter.
7.8.7. 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.
7.8.8. 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 Attribute, 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 MyDomain\Model\UserRepository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
}
class UserRepository extends EntityRepository
{
/** @return Collection<User> */
public function getAllAdminUsers(): Collection
{
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();