1
0
mirror of synced 2024-12-15 07:36:03 +03:00
doctrine2/manual/en/working-with-objects.txt
2010-07-23 23:43:44 +02:00

720 lines
32 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.
++ 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.
> **CAUTION**
> 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();
> **CAUTION**
> 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.
* 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.
> **CAUTION**
> 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.
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").
* 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 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.
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`) completly 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:
[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:
[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.
> **CAUTION**
> 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:
* If X is a detached entity, the state of X is copied onto a pre-existing managed entity instance X' of the same identity or a new managed copy X' of X is created.
* If X is a new entity instance, an InvalidArgumentException will be thrown.
* 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 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.
> **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.
++ Associations
Associations between entities are represented just like in regular object-oriented PHP, with references to other objects or collections of objects. When it comes to persistence, it is important to understand three main things:
* The concept of owning and inverse sides in bidirectional associations as described [here](http://www.doctrine-project.org/documentation/manual/2_0/en/association-mapping#owning-side-and-inverse-side).
* If an entity is removed from a collection, the association is removed, not the entity itself. A collection of entities always only represents the association to the containing entities, not the entity itself.
* Collection-valued persistent fields have to be instances of the `Doctrine\Common\Collections\Collection` interface. [See here](http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities:persistent-fields) for more details.
+++ Example Associations
We will use a simple comment system with Users and Comments as entity to show examples of association management:
[php]
/** @Entity */
class User
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
*
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
* @JoinTable(name="user_favorite_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")}
* )
*/
private $favorites;
/**
* Unidirectional - Many users have marked many comments as read
*
* @ManyToMany(targetEntity="Comment")
* @JoinTable(name="user_read_comments",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")}
* )
*/
private $commentsRead;
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author")
*/
private $commentsAuthored;
/**
* Unidirectional - Many-To-One
*
* @ManyToOne(targetEntity="Comment")
*/
private $firstComment;
}
/** @Entity */
class Comment
{
/** @Id @GeneratedValue @Column(type="string") */
private $id;
/**
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
*
* @ManyToMany(targetEntity="User", mappedBy="favorites")
*/
private $userFavorites;
/**
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
*
* @ManyToOne(targetEntity="User", inversedBy="authoredComments")
*/
private $author;
}
This two entities generate the following MySQL Schema (Foreign Key definitions omitted):
[sql]
CREATE TABLE User (
id VARCHAR(255) NOT NULL,
firstComment_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE Comment (
id VARCHAR(255) NOT NULL,
author_id VARCHAR(255) DEFAULT NULL,
PRIMARY KEY(id)
) ENGINE = InnoDB;
CREATE TABLE user_favorite_comments (
user_id VARCHAR(255) NOT NULL,
favorite_comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, favorite_comment_id)
) ENGINE = InnoDB;
CREATE TABLE user_read_comments (
user_id VARCHAR(255) NOT NULL,
comment_id VARCHAR(255) NOT NULL,
PRIMARY KEY(user_id, comment_id)
) ENGINE = InnoDB;
++ Establishing Associations
Establishing an association between two entities is straight-forward. Here are some examples for the unidirectional
relations of the `User`:
[php]
class User
{
// ...
public function getReadComments() {
return $this->commentsRead;
}
public function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
}
// unidirectional many to many
$user->getReadComments()->add($comment);
// unidirectional many to one
$user->setFirstComment($myFirstComment);
In the case of bi-directional associations you have to update the fields on both sides:
[php]
class User
{
// ..
public function getAuthoredComments() {
return $this->commentsAuthored;
}
public function getFavoriteComments() {
return $this->favorites;
}
}
class Comment
{
// ...
public function getUserFavorites() {
return $this->userFavorites;
}
public function setAuthor(User $author = null) {
$this->author = $author;
}
}
// Many-to-Many
$user->getFavorites()->add($favoriteComment);
$favoriteComment->getUserFavorites()->add($user);
// Many-To-One / One-To-Many Bidirectional
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
Notice how always both sides of the bidirectional association are updated. The previous unidirectional associations were simpler to handle.
++ Removing Associations
Removing an association between two entities is similarly straight-forward. There are two strategies
to do so, by key and by element. Here are some examples:
[php]
// Remove by Elements
$user->getComments()->removeElement($comment);
$comment->setAuthor(null);
$user->getFavorites()->removeElement($comment);
$comment->getUserFavorites()->removeElement($user);
// Remove by Key
$user->getComments()->removeElement($ithComment);
$comment->setAuthor(null);
Notice how both sides of the bidirectional association are always updated. Unidirectional associations are consequently
simpler to handle. Also note that if you type-hint your methods, i.e. `setAddress(Address $address)`, then PHP does only
allows null values if `null` is set as default value. Otherwise setAddress(null) will fail for removing the association.
If you insist on type-hinting a typical way to deal with this is to provide a special method, like `removeAddress()`.
This can also provide better encapsulation as it hides the internal meaning of not having an address.
When working with collections, keep in mind that a Collection is essentially an ordered map (just like a PHP array).
That is why the `remove` operation accepts an index/key. `removeElement` is a separate method
that has O(n) complexity using `array_search`, where n is the size of the map.
> **NOTE**
>
> Since Doctrine always only looks at the owning side of a bidirectional association for updates, it is not necessary
> for write operations that an inverse collection of a bidirectional one-to-many or many-to-many association is updated.
> This knowledge can often be used to improve performance by avoiding the loading of the inverse collection.
> **NOTE**
>
> You can also clear the contents of a whole collection using the `Collections::clear()` method. You
> should be aware that using this method can lead to a straight and optimized database delete or update call
> during the flush operation that is not aware of entities that have been re-added to the collection.
>
> Say you clear a collection of tags by calling `$post->getTags()->clear();` and then call
> `$post->getTags()->add($tag)`. This will not recognize tag being already added before and issue
> two database calls.
++ Association Management Methods
It is generally a good idea to encapsulate proper association management inside the entity classes. This makes it easier to use the class correctly and can encapsulate details about how the association is maintained.
The following code shows updates to the previous User and Comment example that encapsulate much of
the association management code:
[php]
class User
{
//...
public function markCommentRead(Comment $comment) {
// Collections implement ArrayAccess
$this->commentsRead[] = $comment;
}
public function addComment(Comment $comment) {
if (count($this->commentsAuthored) == 0) {
$this->setFirstComment($comment);
}
$this->comments[] = $comment;
$comment->setAuthor($this);
}
private function setFirstComment(Comment $c) {
$this->firstComment = $c;
}
public function addFavorite(Comment $comment) {
$this->favorites->add($comment);
$comment->addUserFavorite($this);
}
public function removeFavorite(Comment $comment) {
$this->favorites->removeElement($comment);
$comment->removeUserFavorite($this);
}
}
class Comment
{
// ..
public function addUserFavorite(User $user) {
$this->userFavorites[] = $user;
}
public function removeUserFavorite(User $user) {
$this->userFavorites->removeElement($user);
}
}
You will notice that `addUserFavorite` and `removeUserFavorite` do not call `addFavorite` and `removeFavorite`,
thus the bidirectional association is strictly-speaking still incomplete. However if you would naively add the
`addFavorite` in `addUserFavorite`, you end up with an infinite loop, so more work is needed.
As you can see, proper bidirectional association management in plain OOP is a non-trivial task
and encapsulating all the details inside the classes can be challenging.
> **NOTE**
>
> If you want to make sure that your collections are perfectly encapsulated you should not return
> them from a `getCollectionName()` method directly, but call `$collection->toArray()`. This way a client programmer
> for the entity cannot circumvent the logic you implement on your entity for association management. For example:
[php]
class User {
public function getReadComments() {
return $this->commentsRead->toArray();
}
}
There is no single, best way for association management. It greatly depends on the requirements of your concrete
domain model as well as your preferences.
++ Transitive persistence
Persisting, removing, detaching and merging individual entities can become pretty
cumbersome, especially when a larger object graph with collections is involved.
Therefore Doctrine 2 provides a mechanism for transitive persistence through
cascading of these operations. Each association to another entity or a collection
of entities can be configured to automatically cascade certain operations. By
default, no operations are cascaded.
The following cascade options exist:
* persist : Cascades persist operations to the associated entities.
* remove : Cascades remove operations to the associated entities.
* merge : Cascades merge operations to the associated entities.
* detach : Cascades detach operations to the associated entities.
* all : Cascades persist, remove, merge and detach operations to associated entities.
The following example shows an association to a number of addresses. If persist()
or remove() is invoked on any User entity, it will be cascaded to all associated
Address entities in the $addresses collection.
[php]
class User
{
//...
/**
* @OneToMany(targetEntity="Address", mappedBy="owner", cascade={"persist", "remove"})
*/
private $addresses;
//...
}
Even though automatic cascading is convenient it should be used with care.
Do not blindly apply cascade=all to all associations as it will unnecessarily
degrade the performance of your application.
++ 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.
The flush operation applies to a managed entity with the following semantics:
* The entity itself is synchronized to the database, if it has changed.
For all (initialized) relationships of the 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()).
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 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:
[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.
> **CAUTION**
> 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.
+++ 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.
+++ 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::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).
++ 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:
[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.
+++ 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'));
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');
+++ 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 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](http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language). 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](http://www.doctrine-project.org/documentation/manual/2_0/en/query-builder).
+++ 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.
[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();