1
0
mirror of synced 2024-12-15 15:46:02 +03:00

Split Working with Objects into two chapters, adding Working with Associations

This commit is contained in:
Benjamin Eberlei 2010-07-24 10:46:54 +02:00
parent 99533afd11
commit 23efc790c1
5 changed files with 453 additions and 353 deletions

View File

@ -5,6 +5,7 @@
+ Association Mapping + Association Mapping
+ Inheritance Mapping + Inheritance Mapping
+ Working with objects + Working with objects
+ Working with associations
+ Transactions and Concurrency + Transactions and Concurrency
+ Events + Events
+ Batch processing + Batch processing

View File

@ -57,7 +57,9 @@ In all the examples of many-valued associations in this manual we will make use
++ Mapping Defaults ++ Mapping Defaults
The @JoinColumn and @JoinTable definitions are usually optional and have sensible default values. The defaults for a join column in a one-to-one/many-to-one association is as follows: Before we introduce all the association mappings in detailyou should note that the @JoinColumn and @JoinTable
definitions are usually optional and have sensible default values.
The defaults for a join column in a one-to-one/many-to-one association is as follows:
name: "<fieldname>_id" name: "<fieldname>_id"
referencedColumnName: "id" referencedColumnName: "id"
@ -573,7 +575,7 @@ Here is a similar many-to-many relationship as above except this one is bidirect
The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above. The MySQL schema is exactly the same as for the Many-To-Many uni-directional case above.
+++ Picking the Owning and Inverse Side +++ Picking Owning and Inverse Side
For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is For Many-To-Many associations you can chose which entity is the owning and which the inverse side. There is
a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective. a very simple semantic rule to decide which side is more suitable to be the owning side from a developers perspective.

View File

@ -289,4 +289,8 @@ but we wanted to introduce you to DQL at this point. Can you **find** the easier
> schema with the command `doctrine orm:schema-tool --drop` followed by > schema with the command `doctrine orm:schema-tool --drop` followed by
> `doctrine orm:schema-tool --create`. > `doctrine orm:schema-tool --create`.
6) Explore Doctrine 2! 6) Explore Doctrine 2!
See the following links if you want to start with more complex tutorials rather than reading the manual:
* Doctrine2 Cookbook: [Getting Started XML Edition](http://www.doctrine-project.org/projects/orm/2.0/docs/cookbook/getting-started-xml-edition/en)

View File

@ -0,0 +1,417 @@
++ 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.
Changes to associations in your code are not synchronized to the database directly, but upon calling `EntityManager#flush()`.
+++ Example: Entities with Associations
We will use a simple comment system with Users and Comments as entities to show examples of association management.
See the docblocks of each association in the following example for information about its type and if its the owning or inverse side.
[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;
}
}
Interaction would then look like the following code parts, `$em` here is an instance of the EntityManager:
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
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);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist();
$m->flush();
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);
You need to call `$em->flush()` to make persist these changes in the database permanently.
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();
}
}
This will however always initialize the collection, with all the performance penalties given the size. In
some scenarios of large collections it might even be a good idea to completely hide the read access behind
methods on the EntityRepository.
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.
++ Synchronizing Bidirectional Collections
In the case of Many-To-Many associations you as the developer are responsible to keep the collections on the
owning and inverse side up in sync, when you apply changes to them. Doctrine can only guarantee a consistent
state for the hydration, not for your client code.
Using the User-Comment entities from above, a very simple example can show the possible caveats you can encounter:
[php]
$user->getFavorites()->add($favoriteComment);
// not calling $favoriteComment->getUserFavorites()->add($user);
$user->getFavorites()->contains($favoriteComment); // TRUE
$favoriteComment->getUerFavorites()->contains($user); // FALSE
There are to approaches to handle this problem in your code:
1. Ignore updating the inverse side of bidirectional collections, BUT never read from them in requests that changed
their state. In the next Request Doctrine hydrates the consistent collection state again.
2. Always keep the bidirectional collections in sync through association management methods. Reads of
the Collections directly after changes are consistent then.
++ Transitive persistence / Cascade Operations
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 is an extension to the User-Comment example of this chapter.
Suppose in our application a user is created whenever he writes his first comment.
In this case we would use the following code:
[php]
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment);
$em->flush();
Even if you *persist* a new User that contains our new Comment this code would fail
if you removed the call to `EntityManager#persist($myFirstComment)`. Doctrine 2 does
not cascade the persist operation to all nested entities that are new as well.
More complicated is the deletion of all a users comments when he is removed from the system:
$user = $em->find('User', $deleteUserId);
foreach ($user->getAuthoredComments() AS $comment) {
$em->remove($comment);
}
$em->remove($user);
$em->flush();
Without the loop over all the authored comments Doctrine would use an UPDATE statement only
to set the foreign key to NULL and only the User would be deleted from the database
during the flush()-Operation.
To have Doctrine handle both cases automatically we can change the `User#commentsAuthored`
property to cascade both the "persist" and the "remove" operation.
[php]
class User
{
//...
/**
* Bidirectional - One-To-Many (INVERSE SIDE)
*
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
*/
private $commentsAuthored;
//...
}
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. For each cascade operation that gets
activated Doctrine also applies that operation to the association, be it
single or collection valued.
+++ Persistence by Reachability: Cascade Persist
There are additional semantics that apply to the Cascade Persist operation.
During each flush() operation Doctrine detects if there are new entities in any
collection and three possible cases can happen:
1. New entities in a collection marked as cascade persist will be directly persisted by Doctrine.
2. New entities in a collection not marked as cascade persist will produce an Exception and rollback the flush() operation.
3. Collections without new entities are skipped.
This concept is called Persistence by Reachability: New entities that are found on
already managed entities are automatically persisted as long as the association is defined
as cascade persist.

View File

@ -9,6 +9,15 @@ 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 changes to objects within this Unit of Work that have not yet been persisted
are lost. 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.
++ Persisting entities ++ Persisting entities
An entity can be made persistent by passing it to the `EntityManager#persist($entity)` An entity can be made persistent by passing it to the `EntityManager#persist($entity)`
@ -174,352 +183,6 @@ and you want to modify and persist such an entity.
> there is no need to use `merge`. I.e. you can simply pass detached objects from a cache > there is no need to use `merge`. I.e. you can simply pass detached objects from a cache
> directly to the view. > 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;
}
}
Interaction would then look like the following code parts, `$em` here is an instance of the EntityManager:
$user = $em->find('User', $userId);
// unidirectional many to many
$comment = $em->find('Comment', $readCommentId);
$user->getReadComments()->add($comment);
$em->flush();
// unidirectional many to one
$myFirstComment = new Comment();
$user->setFirstComment($myFirstComment);
$em->persist($myFirstComment);
$em->flush();
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);
$em->flush();
// Many-To-One / One-To-Many Bidirectional
$newComment = new Comment();
$user->getAuthoredComments()->add($newComment);
$newComment->setAuthor($user);
$em->persist();
$m->flush();
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);
You need to call `$em->flush()` to make persist these changes in the database permanently.
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();
}
}
This will however always initialize the collection, with all the performance penalties given the size. In
some scenarios of large collections it might even be a good idea to completely hide the read access behind
methods on the EntityRepository.
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 ++ Synchronization with the Database
The state of persistent entities is synchronized with the database on flush of an `EntityManager` The state of persistent entities is synchronized with the database on flush of an `EntityManager`
@ -528,11 +191,21 @@ persistent entities and their relationships to the database. Thereby bidirection
are persisted based on the references held by the owning side of the relationship as explained are persisted based on the references held by the owning side of the relationship as explained
in the Association Mapping chapter. in the Association Mapping chapter.
When `EntityManager#flush()` is called, Doctrine inspects all managed, new and removed entities
and will perform the following operations.
+++ Synchronizing New and Managed Entities
The flush operation applies to a managed entity with the following semantics: The flush operation applies to a managed entity with the following semantics:
* The entity itself is synchronized to the database, if it has changed. * 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.
For all (initialized) relationships of the entity the following semantics apply to each 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: associated entity X:
* If X is new and persist operations are configured to cascade on the relationship, * If X is new and persist operations are configured to cascade on the relationship,
@ -542,10 +215,13 @@ associated entity X:
* If X is removed and persist operations are configured to cascade on the relationship, * 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). 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, * 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()). 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. 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. 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