Bi-Directional References¶
By default when you map a bi-directional reference, the reference is maintained on both sides of the relationship and there is not a single "owning side". Both sides are considered owning and changes are tracked and persisted separately. Here is an example:
<?php
/** @Document */
class BlogPost
{
// ...
/** @ReferenceOne(targetDocument="User") */
private $user;
}
/** @Document */
class User
{
// ...
/** @ReferenceMany(targetDocument="BlogPost") */
private $posts;
}
When I persist some instances of the above classes the references would exist on both sides! The
BlogPost collection would have a DBRef stored on the $user property and the User
collection would have a DBRef stored in the $posts property.
Owning and Inverse Sides¶
A user may have lots of posts and we don't need to store a reference to each post on the user, we can get the users post by running a query like the following:
db.BlogPost.find({ 'user.$id' : user.id })
In order to map this you can use the inversedBy and mappedBy options. Here is the same
example above where we implement this:
One to Many¶
<?php
/** @Document */
class BlogPost
{
// ...
/** @ReferenceOne(targetDocument="User", inversedBy="posts") */
private $user;
}
/** @Document */
class User
{
// ...
/** @ReferenceMany(targetDocument="BlogPost", mappedBy="user") */
private $posts;
}
So now when we persist a User and multiple BlogPost instances for that User:
<?php
$user = new User();
$post1 = new BlogPost();
$post1->setUser($user);
$post2 = new BlogPost();
$post2->setUser($user);
$post3 = new BlogPost();
$post3->setUser($user);
$dm->persist($post1);
$dm->persist($post2);
$dm->persist($post3);
$dm->flush();
And we retrieve the User later to access the posts for that user:
<?php
$user = $dm->find('User', $user->id);
$posts = $user->getPosts();
foreach ($posts as $post) {
// ...
}
The above will execute a query like the following to lazily load the collection of posts to iterate over:
db.BlogPost.find( { 'user.$id' : user.id } )
Note
Remember that the inverse side, the side which specified mappedBy is immutable and
any changes to the state of the reference will not be persisted.
Other Examples¶
Here are several examples which implement the inversedBy and mappedBy options:
One to One¶
Here is an example where we have a one to one relationship between Cart and Customer:
<?php
/** @Document */
class Cart
{
// ...
/**
* @ReferenceOne(targetDocument="Customer", inversedBy="cart")
*/
public $customer;
}
/** @Document */
class Customer
{
// ...
/**
* @ReferenceOne(targetDocument="Cart", mappedBy="customer")
*/
public $cart;
}
The owning side is on Cart.customer and the Customer.cart referenced is loaded with a query
like this:
db.Cart.find( { 'customer.$id' : customer.id } )
If you want to nullify the relationship between a Cart instance and Customer instance
you must null it out on the Cart.customer side:
<?php
$cart->setCustomer(null);
$dm->flush();
Note
When specifying inverse one-to-one relationships the referenced document is
loaded directly when the owning document is hydrated instead of using a
proxy. In the example above, loading a Customer object from the database
would also cause the corresponding Cart to be loaded. This can cause
performance issues when loading many Customer objects at once.
Self-Referencing Many to Many¶
<?php
namespace Documents;
/** @Document */
class User
{
// ...
/**
* @ReferenceMany(targetDocument="User", mappedBy="myFriends")
*/
public $friendsWithMe;
/**
* @ReferenceMany(targetDocument="User", inversedBy="friendsWithMe")
*/
public $myFriends;
public function __construct($name)
{
$this->name = $name;
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addFriend(User $user)
{
$user->friendsWithMe[] = $this;
$this->myFriends[] = $user;
}
}
