Reference Mapping
This chapter explains how references between documents are mapped with Doctrine.
Collections
Examples of many-valued references in this manual make use of a Collection
interface and a corresponding ArrayCollection
implementation, which are
defined in the Doctrine\Common\Collections
namespace. These classes have no
dependencies on ODM, and can therefore be used within your domain model and
elsewhere without introducing coupling to the persistence layer.
ODM also provides a PersistentCollection
implementation of Collection
,
which incorporates change-tracking functionality; however, this class is
constructed internally during hydration. As a developer, you should develop with
the Collection
interface in mind so that your code can operate with any
implementation.
New in 1.1: you are no longer limited to using |
Why are these classes used over PHP arrays? Native arrays cannot be
transparently extended in PHP, which is necessary for many advanced features
provided by the ODM. Although PHP does provide various interfaces that allow
objects to operate like arrays (e.g. Traversable
, Countable
,
ArrayAccess
), and even a concrete implementation in ArrayObject
, these
objects cannot always be used everywhere that a native array is accepted.
Doctrine's Collection
interface and ArrayCollection
implementation are
conceptually very similar to ArrayObject
, with some slight differences and
improvements.
Reference Many
Reference many documents:
1 <?php
#[Document]
class User
{
// ...
/** @var Collection<Account> */
#[ReferenceMany(targetDocument: Account::class)]
private Collection $accounts;
public function __construct()
{
$this->accounts = new ArrayCollection();
}
// ...
}
#[Document]
class Account
{
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Mixing Document Types
If you want to store different types of documents in references, you can simply
omit the targetDocument
option:
Now the $favorites
property can store a reference to any type of document!
The class name will be automatically stored in a field named
_doctrine_class_name
within the DBRef object.
The MongoDB shell tends to ignore fields other than |
The name of the field within the DBRef object can be customized via the
discriminatorField
option:
You can also specify a discriminator map to avoid storing the FQCN in each DBRef object:
If you have references without a discriminator value that should be considered a certain class, you can optionally specify a default discriminator value:
Storing References
By default all references are stored as a DBRef object with the traditional
$ref
, $id
, and (optionally) $db
fields (in that order). For references to
documents of a single collection, storing the collection (and database) names for
each reference may be redundant. You can use ID references to store the
referenced document's identifier (e.g. MongoDB\BSON\ObjectId
) instead of a
DBRef.
Example:
Now, the profile
field will only store the MongoDB\BSON\ObjectId
of the
referenced Profile document.
ID references reduce the amount of storage used, both for the document itself and any indexes on the reference field; however, ID references cannot be used with discriminators, since there is no DBRef object in which to store a discriminator value.
In addition to saving references as DBRef with $ref
, $id
, and $db
fields and as MongoDB\BSON\ObjectId
, it is possible to save references as
DBRef without the $db
field. This solves problems when the database name
changes (and also reduces the amount of storage used).
The storeAs
option has the following possible values:
- dbRefWithDb: Uses a DBRef with
$ref
,$id
, and$db
fields - dbRef: Uses a DBRef with
$ref
and$id
(this is the default) - ref: Uses a custom embedded object with an
id
field - id: Uses the identifier of the referenced object
Up until 2.0 |
Cascading Operations
By default, Doctrine will not cascade any UnitOfWork
operations to
referenced documents. You must explicitly enable this functionality:
The valid values are:
- all - cascade all operations by default.
- detach - cascade detach operation to referenced documents.
- merge - cascade merge operation to referenced documents.
- refresh - cascade refresh operation to referenced documents.
- remove - cascade remove operation to referenced documents.
- persist - cascade persist operation to referenced documents.
Orphan Removal
There is another concept of cascading that is relevant only when removing documents
from collections. If a Document of type A
contains references to privately
owned Documents B
then if the reference from A
to B
is removed the
document B
should also be removed, because it is not used anymore.
OrphanRemoval works with both reference one and many mapped fields.
When using the |
As a better example consider an Addressbook application where you have Contacts, Addresses and StandingData:
1 <?php
namespace Addressbook;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[Document]
class Contact
{
#[Id]
private string $id;
#[ReferenceOne(targetDocument: StandingData::class, orphanRemoval: true)]
private ?StandingData $standingData;
#[ReferenceMany(targetDocument: Address::class, mappedBy: 'contact', orphanRemoval: true)]
private Collection $addresses;
public function __construct()
{
$this->addresses = new ArrayCollection();
}
public function newStandingData(StandingData $sd): void
{
$this->standingData = $sd;
}
public function removeAddress($pos): void
{
unset($this->addresses[$pos]);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Now two examples of what happens when you remove the references:
In this case you have not only changed the Contact
document itself but
you have also removed the references for standing data and as well as one
address reference. When flush is called not only are the references removed
but both the old standing data and the one address documents are also deleted
from the database.
Storing Empty Arrays
By default, when a collection property is empty, Doctrine does not store any data for it in the database.
However, in some cases, you may want to explicitly store an empty array for such properties.
You can achieve this behavior by using the storeEmptyArray
option.
Now, when the $accounts
collection is empty, an empty array will be stored in the database for the User
document,
even if there are no actual referenced documents.