Skip to content

Commit

Permalink
Merge pull request #1349 from coudenysj/feature/reference-storeAs
Browse files Browse the repository at this point in the history
storeAs option for references
  • Loading branch information
alcaeus committed May 13, 2016
2 parents 9a7ffff + bde7416 commit a4d5226
Show file tree
Hide file tree
Showing 27 changed files with 612 additions and 54 deletions.
14 changes: 10 additions & 4 deletions docs/en/reference/annotations-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1191,8 +1191,11 @@ Optional attributes:
-
targetDocument - A |FQCN| of the target document.
-
simple - Create simple references and only store the referenced document's
identifier (e.g. ``MongoId``) instead of a `DBRef`_. Note that simple
simple - deprecated (use ``storeAs: id``)
-
storeAs - Indicates how to store the reference. ``id`` uses ``MongoId``,
``dbRef`` uses a `DBRef`_ without ``$db`` value and ``dbRefWithDb`` stores
a full `DBRef`_ (``$ref``, ``$id``, and ``$db``). Note that ``id``
references are not compatible with the discriminators.
-
cascade - Cascade Option
Expand Down Expand Up @@ -1258,8 +1261,11 @@ Optional attributes:
-
targetDocument - A |FQCN| of the target document.
-
simple - Create simple references and only store the referenced document's
identifier (e.g. ``MongoId``) instead of a `DBRef`_. Note that simple
simple - deprecated (use ``storeAs: id``)
-
storeAs - Indicates how to store the reference. ``id`` uses ``MongoId``,
``dbRef`` uses a `DBRef`_ without ``$db`` value and ``dbRefWithDb`` stores
a full `DBRef`_ (``$ref``, ``$id``, and ``$db``). Note that ``id``
references are not compatible with the discriminators.
-
cascade - Cascade Option
Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/complex-references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ the inverse side of a relationship.
You can create an `immutable`_ reference to one or many documents and specify
how that reference is to be loaded. The reference is immutable in that it is
defined only in the mapping, unlike a typical reference where a `MongoDBRef`_ or
identifier (for :ref:`simple_references`) is stored on the document itself.
identifier (see :ref:`storing_references`) is stored on the document itself.

The following options may be used for :ref:`one <reference_one>` and
:ref:`many <reference_many>` reference mappings:
Expand Down
2 changes: 1 addition & 1 deletion docs/en/reference/priming-references.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ queries (one per discriminated class name).

.. note::

Priming is also compatible with :ref:`simple references <simple_references>`
Priming is also compatible with :ref:`simple references <storing_references>`
and discriminated references. When priming discriminated references, ODM
will issue one query per distinct class among the referenced document(s).

Expand Down
40 changes: 31 additions & 9 deletions docs/en/reference/reference-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -309,14 +309,14 @@ a certain class, you can optionally specify a default discriminator value:
song: Documents\Song
defaultDiscriminatorValue: album
.. _simple_references:
.. _storing_references:

Simple References
-----------------
Storing References
------------------

By default all references are stored as a `DBRef`_ object with the traditional
``$ref``, ``$id``, and ``$db`` fields (in that order). For references to
documents of a single collection, storing the collection and database names for
``$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 simple references to store the
referenced document's identifier (e.g. ``MongoId``) instead of a `DBRef`_.

Expand All @@ -329,19 +329,19 @@ Example:
<?php
/**
* @ReferenceOne(targetDocument="Profile", simple=true)
* @ReferenceOne(targetDocument="Profile", storeAs="id")
*/
private $profile;
.. code-block:: xml
<reference-one target-document="Documents\Profile", simple="true" />
<reference-one target-document="Documents\Profile", store-as="id" />
.. code-block:: yaml
referenceOne:
profile:
simple: true
storeAs: id
Now, the ``profile`` field will only store the ``MongoId`` of the referenced
Profile document.
Expand All @@ -351,6 +351,28 @@ itself and any indexes on the reference field; however, simple 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 ``MongoId``, 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 three possible values:

- **dbRefWithDb**: Uses a `DBRef`_ with ``$ref``, ``$id``, and ``$db`` fields (this is the default)
- **dbRef**: Uses a `DBRef`_ with ``$ref`` and ``$id``
- **id**: Uses a ``MongoId``

.. note::

The ``storeAs=id`` option used to be called a "simple reference". The old syntax is
still recognized (so using ``simple=true`` will imply ``storeAs=id``).

.. note::

For backwards compatibility ``storeAs=dbRefWithDb`` is the default, but
``storeAs=dbRef`` is the recommended setting.


Cascading Operations
--------------------

Expand Down Expand Up @@ -391,6 +413,6 @@ The valid values are:
- **remove** - cascade remove operation to referenced documents.
- **persist** - cascade persist operation to referenced documents.

.. _`DBRef`: http://docs.mongodb.org/manual/reference/database-references/#dbref
.. _`DBRef`: http://docs.mongodb.org/manual/reference/database-references/#dbrefs
.. |FQCN| raw:: html
<abbr title="Fully-Qualified Class Name">FQCN</abbr>
14 changes: 12 additions & 2 deletions doctrine-mongo-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@
<xs:attribute name="collectionClass" type="xs:string" />
</xs:complexType>

<xs:simpleType name="reference-store-as">
<xs:restriction base="xs:token">
<xs:enumeration value="id"/>
<xs:enumeration value="dbRef"/>
<xs:enumeration value="dbRefWithDb"/>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="reference-one">
<xs:sequence>
<xs:element name="cascade" type="odm:cascade-type" minOccurs="0" />
Expand All @@ -125,7 +133,8 @@
<xs:attribute name="target-document" type="xs:string" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="fieldName" type="xs:NMTOKEN" />
<xs:attribute name="simple" type="xs:boolean" />
<xs:attribute name="simple" type="xs:boolean" /><!-- deprecated -->
<xs:attribute name="store-as" type="odm:reference-store-as" default="dbRefWithDb" />
<xs:attribute name="strategy" type="xs:NMTOKEN" default="pushAll" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
Expand All @@ -145,7 +154,8 @@
<xs:attribute name="target-document" type="xs:string" />
<xs:attribute name="field" type="xs:NMTOKEN" use="required" />
<xs:attribute name="fieldName" type="xs:NMTOKEN" />
<xs:attribute name="simple" type="xs:boolean" />
<xs:attribute name="simple" type="xs:boolean" /><!-- deprecated -->
<xs:attribute name="store-as" type="odm:reference-store-as" default="dbRefWithDb" />
<xs:attribute name="strategy" type="xs:NMTOKEN" default="pushAll" />
<xs:attribute name="inversed-by" type="xs:NMTOKEN" />
<xs:attribute name="mapped-by" type="xs:NMTOKEN" />
Expand Down
7 changes: 5 additions & 2 deletions lib/Doctrine/ODM/MongoDB/DocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,7 @@ public function createDBRef($document, array $referenceMapping = null)
);
}

if ( ! empty($referenceMapping['simple'])) {
if ($referenceMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
if ($class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
throw MappingException::simpleReferenceMustNotTargetDiscriminatedDocument($referenceMapping['targetDocument']);
}
Expand All @@ -689,9 +689,12 @@ public function createDBRef($document, array $referenceMapping = null)
$dbRef = array(
'$ref' => $class->getCollection(),
'$id' => $class->getDatabaseIdentifierValue($id),
'$db' => $this->getDocumentDatabase($class->name)->getName(),
);

if ($referenceMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB) {
$dbRef['$db'] = $this->getDocumentDatabase($class->name)->getName();
}

/* If the class has a discriminator (field and value), use it. A child
* class that is not defined in the discriminator map may only have a
* discriminator field and no value, so default to the full class name.
Expand Down
5 changes: 3 additions & 2 deletions lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ private function generateHydratorClass(ClassMetadata $class, $hydratorClassName,
/** @ReferenceOne */
if (isset(\$data['%1\$s'])) {
\$reference = \$data['%1\$s'];
if (isset(\$this->class->fieldMappings['%2\$s']['simple']) && \$this->class->fieldMappings['%2\$s']['simple']) {
if (isset(\$this->class->fieldMappings['%2\$s']['storeAs']) && \$this->class->fieldMappings['%2\$s']['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
\$className = \$this->class->fieldMappings['%2\$s']['targetDocument'];
\$mongoId = \$reference;
} else {
Expand Down Expand Up @@ -291,7 +291,7 @@ private function generateHydratorClass(ClassMetadata $class, $hydratorClassName,
\$className = \$mapping['targetDocument'];
\$targetClass = \$this->dm->getClassMetadata(\$mapping['targetDocument']);
\$mappedByMapping = \$targetClass->fieldMappings[\$mapping['mappedBy']];
\$mappedByFieldName = isset(\$mappedByMapping['simple']) && \$mappedByMapping['simple'] ? \$mapping['mappedBy'] : \$mapping['mappedBy'].'.\$id';
\$mappedByFieldName = isset(\$mappedByMapping['storeAs']) && \$mappedByMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID ? \$mapping['mappedBy'] : \$mapping['mappedBy'].'.\$id';
\$criteria = array_merge(
array(\$mappedByFieldName => \$data['_id']),
isset(\$this->class->fieldMappings['%2\$s']['criteria']) ? \$this->class->fieldMappings['%2\$s']['criteria'] : array()
Expand Down Expand Up @@ -366,6 +366,7 @@ private function generateHydratorClass(ClassMetadata $class, $hydratorClassName,
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Hydrator\HydratorInterface;
use Doctrine\ODM\MongoDB\UnitOfWork;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
/**
* THIS CLASS WAS GENERATED BY THE DOCTRINE ODM. DO NOT EDIT THIS FILE.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

namespace Doctrine\ODM\MongoDB\Mapping\Annotations;

use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;

/**
Expand All @@ -30,7 +31,8 @@ final class ReferenceMany extends AbstractField
{
public $type = 'many';
public $reference = true;
public $simple = false;
public $simple = false; // @deprecated
public $storeAs = ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB;
public $targetDocument;
public $discriminatorField;
public $discriminatorMap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

namespace Doctrine\ODM\MongoDB\Mapping\Annotations;

use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;

/**
* Specifies a one-to-one relationship to a different document
*
Expand All @@ -28,7 +30,8 @@ final class ReferenceOne extends AbstractField
{
public $type = 'one';
public $reference = true;
public $simple = false;
public $simple = false; // @deprecated
public $storeAs = ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB;
public $targetDocument;
public $discriminatorField;
public $discriminatorMap;
Expand Down
22 changes: 21 additions & 1 deletion lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ class ClassMetadataInfo implements \Doctrine\Common\Persistence\Mapping\ClassMet
const MANY = 'many';
const ONE = 'one';

/**
* The types of storeAs references
*/
const REFERENCE_STORE_AS_ID = 'id';
const REFERENCE_STORE_AS_DB_REF = 'dbRef';
const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';

/* The inheritance mapping types */
/**
* NONE means the class does not participate in an inheritance hierarchy
Expand Down Expand Up @@ -1181,7 +1188,20 @@ public function mapField(array $mapping)
$mapping['nullable'] = false;
}

if (isset($mapping['reference']) && ! empty($mapping['simple']) && ! isset($mapping['targetDocument'])) {
// Synchronize the "simple" and "storeAs" mapping information for backwards compatibility
if (isset($mapping['simple']) && ($mapping['simple'] === true || $mapping['simple'] === 'true')) {
$mapping['storeAs'] = ClassMetadataInfo::REFERENCE_STORE_AS_ID;
}
// Remove the "simple" mapping and use "storeAs" in all further logic
if (isset($mapping['simple'])) {
unset($mapping['simple']);
}

if (isset($mapping['reference'])
&& isset($mapping['storeAs'])
&& $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID
&& ! isset($mapping['targetDocument'])
) {
throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
}

Expand Down
3 changes: 2 additions & 1 deletion lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ private function addReferenceMapping(ClassMetadataInfo $class, $reference, $type
'orphanRemoval' => isset($attributes['orphan-removal']) ? ('true' === (string) $attributes['orphan-removal']) : false,
'type' => $type,
'reference' => true,
'simple' => isset($attributes['simple']) ? ('true' === (string) $attributes['simple']) : false,
'simple' => isset($attributes['simple']) ? ('true' === (string) $attributes['simple']) : false, // deprecated
'storeAs' => isset($attributes['store-as']) ? (string) $attributes['store-as'] : ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB,
'targetDocument' => isset($attributes['target-document']) ? (string) $attributes['target-document'] : null,
'name' => (string) $attributes['field'],
'strategy' => isset($attributes['strategy']) ? (string) $attributes['strategy'] : $defaultStrategy,
Expand Down
3 changes: 2 additions & 1 deletion lib/Doctrine/ODM/MongoDB/Mapping/Driver/YamlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ private function addMappingFromReference(ClassMetadataInfo $class, $fieldName, $
'orphanRemoval' => isset($reference['orphanRemoval']) ? $reference['orphanRemoval'] : false,
'type' => $type,
'reference' => true,
'simple' => isset($reference['simple']) ? (boolean) $reference['simple'] : false,
'simple' => isset($reference['simple']) ? (boolean) $reference['simple'] : false, // deprecated
'storeAs' => isset($reference['storeAs']) ? (string) $reference['storeAs'] : ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB,
'targetDocument' => isset($reference['targetDocument']) ? $reference['targetDocument'] : null,
'fieldName' => $fieldName,
'strategy' => isset($reference['strategy']) ? (string) $reference['strategy'] : $defaultStrategy,
Expand Down
9 changes: 5 additions & 4 deletions lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use Doctrine\MongoDB\CursorInterface;
use Doctrine\ODM\MongoDB\Cursor;
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
use Doctrine\ODM\MongoDB\LockException;
Expand Down Expand Up @@ -666,7 +667,7 @@ private function loadReferenceManyCollectionOwningSide(PersistentCollectionInter
$sorted = isset($mapping['sort']) && $mapping['sort'];

foreach ($collection->getMongoData() as $key => $reference) {
if (isset($mapping['simple']) && $mapping['simple']) {
if (isset($mapping['storeAs']) && $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
$className = $mapping['targetDocument'];
$mongoId = $reference;
} else {
Expand Down Expand Up @@ -752,7 +753,7 @@ public function createReferenceManyInverseSideQuery(PersistentCollectionInterfac
$ownerClass = $this->dm->getClassMetadata(get_class($owner));
$targetClass = $this->dm->getClassMetadata($mapping['targetDocument']);
$mappedByMapping = isset($targetClass->fieldMappings[$mapping['mappedBy']]) ? $targetClass->fieldMappings[$mapping['mappedBy']] : array();
$mappedByFieldName = isset($mappedByMapping['simple']) && $mappedByMapping['simple'] ? $mapping['mappedBy'] : $mapping['mappedBy'] . '.$id';
$mappedByFieldName = isset($mappedByMapping['storeAs']) && $mappedByMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID ? $mapping['mappedBy'] : $mapping['mappedBy'] . '.$id';
$criteria = $this->cm->merge(
array($mappedByFieldName => $ownerClass->getIdentifierObject($owner)),
$this->dm->getFilterCollection()->getFilterCriteria($targetClass),
Expand Down Expand Up @@ -994,7 +995,7 @@ private function prepareQueryElement($fieldName, $value = null, $class = null, $
// No further preparation unless we're dealing with a simple reference
// We can't have expressions in empty() with PHP < 5.5, so store it in a variable
$arrayValue = (array) $value;
if (empty($mapping['reference']) || empty($mapping['simple']) || empty($arrayValue)) {
if (empty($mapping['reference']) || $mapping['storeAs'] !== ClassMetadataInfo::REFERENCE_STORE_AS_ID || empty($arrayValue)) {
return array($fieldName, $value);
}

Expand Down Expand Up @@ -1106,7 +1107,7 @@ private function prepareQueryElement($fieldName, $value = null, $class = null, $
$objectPropertyIsId = $targetClass->isIdentifier($objectProperty);

// Prepare DBRef identifiers or the mapped field's property path
$fieldName = ($objectPropertyIsId && ! empty($mapping['reference']) && empty($mapping['simple']))
$fieldName = ($objectPropertyIsId && ! empty($mapping['reference']) && $mapping['storeAs'] !== ClassMetadataInfo::REFERENCE_STORE_AS_ID)
? $e[0] . '.$id'
: $e[0] . '.' . $objectPropertyPrefix . $targetMapping['name'];

Expand Down
15 changes: 13 additions & 2 deletions lib/Doctrine/ODM/MongoDB/Query/Expr.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
use Doctrine\ODM\MongoDB\Mapping\MappingException;

/**
Expand Down Expand Up @@ -73,12 +74,17 @@ public function references($document)
if ($this->currentField) {
$mapping = $this->getReferenceMapping();
$dbRef = $this->dm->createDBRef($document, $mapping);
$storeAs = array_key_exists('storeAs', $mapping) ? $mapping['storeAs'] : null;

if (isset($mapping['simple']) && $mapping['simple']) {
if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
$this->query[$mapping['name']] = $dbRef;
} else {
$keys = array('ref' => true, 'id' => true, 'db' => true);

if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF) {
unset($keys['db']);
}

if (isset($mapping['targetDocument'])) {
unset($keys['ref'], $keys['db']);
}
Expand Down Expand Up @@ -106,12 +112,17 @@ public function includesReferenceTo($document)
if ($this->currentField) {
$mapping = $this->getReferenceMapping();
$dbRef = $this->dm->createDBRef($document, $mapping);
$storeAs = array_key_exists('storeAs', $mapping) ? $mapping['storeAs'] : null;

if (isset($mapping['simple']) && $mapping['simple']) {
if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
$this->query[$mapping['name']] = $dbRef;
} else {
$keys = array('ref' => true, 'id' => true, 'db' => true);

if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF) {
unset($keys['db']);
}

if (isset($mapping['targetDocument'])) {
unset($keys['ref'], $keys['db']);
}
Expand Down
Loading

0 comments on commit a4d5226

Please sign in to comment.