Skip to content

Commit

Permalink
Add generic reference as replacement for DBRef
Browse files Browse the repository at this point in the history
  • Loading branch information
alcaeus committed Jul 26, 2017
1 parent 0e87292 commit 51ad6f0
Show file tree
Hide file tree
Showing 16 changed files with 383 additions and 57 deletions.
13 changes: 7 additions & 6 deletions docs/en/reference/annotations-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1218,10 +1218,11 @@ Optional attributes:
-
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.
storeAs - Indicates how to store the reference. ``id`` stores the identifier,
``ref`` an embedded object containing the ``id`` field and (optionally) a
discriminator. ``dbRef`` and ``dbRefWithDb`` store a ``DBRef`` object. They
are deprecated in favor of ``ref``. Note that ``id`` references are not
compatible with the discriminators.
-
cascade - Cascade Option
-
Expand Down Expand Up @@ -1293,8 +1294,8 @@ Optional attributes:
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``
``ref`` stores fields ``id`` and ``ref`` (similar to DBRef without $ prefix) value and ``refWithDb`` stores
a additionally db as parameter. ``dbRef`` and ``dbRefWithDb`` use ``DBRef``, they are deprecated in favor of ref and refWithDb. Note that ``id``
references are not compatible with the discriminators.
-
cascade - Cascade Option
Expand Down
1 change: 1 addition & 0 deletions docs/en/reference/reference-mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ 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``
- **ref**: Uses a custom embedded object with an ``id`` field
- **id**: Uses a ``MongoId``

.. note::
Expand Down
1 change: 1 addition & 0 deletions doctrine-mongo-mapping.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
<xs:simpleType name="reference-store-as">
<xs:restriction base="xs:token">
<xs:enumeration value="id"/>
<xs:enumeration value="ref"/>
<xs:enumeration value="dbRef"/>
<xs:enumeration value="dbRefWithDb"/>
</xs:restriction>
Expand Down
30 changes: 24 additions & 6 deletions lib/Doctrine/ODM/MongoDB/Aggregation/Stage/Lookup.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,26 +96,44 @@ private function fromReference($fieldName)
parent::from($targetMapping->getCollection());

if ($referenceMapping['isOwningSide']) {
if ($referenceMapping['storeAs'] !== ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
throw MappingException::cannotLookupNonIdReference($this->class->name, $fieldName);
switch ($referenceMapping['storeAs']) {
case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
$referencedFieldName = $referenceMapping['name'];
break;

case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
$referencedFieldName = $referenceMapping['name'] . '.id';
break;

default:
throw MappingException::cannotLookupNonIdReference($this->class->name, $fieldName);
}

$this
->foreignField('_id')
->localField($referenceMapping['name']);
->localField($referencedFieldName);
} else {
if (isset($referenceMapping['repositoryMethod'])) {
throw MappingException::repositoryMethodLookupNotAllowed($this->class->name, $fieldName);
}

$mappedByMapping = $targetMapping->getFieldMapping($referenceMapping['mappedBy']);
if ($mappedByMapping['storeAs'] !== ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
throw MappingException::cannotLookupNonIdReference($this->class->name, $fieldName);
switch ($mappedByMapping['storeAs']) {
case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
$referencedFieldName = $mappedByMapping['name'];
break;

case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
$referencedFieldName = $mappedByMapping['name'] . '.id';
break;

default:
throw MappingException::cannotLookupNonIdReference($this->class->name, $fieldName);
}

$this
->localField('_id')
->foreignField($mappedByMapping['name']);
->foreignField($referencedFieldName);
}

return $this;
Expand Down
36 changes: 22 additions & 14 deletions lib/Doctrine/ODM/MongoDB/DocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -691,20 +691,28 @@ public function createDBRef($document, array $referenceMapping = null)
);
}

if ($referenceMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
if ($class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
throw MappingException::simpleReferenceMustNotTargetDiscriminatedDocument($referenceMapping['targetDocument']);
}
return $class->getDatabaseIdentifierValue($id);
}

$dbRef = array(
'$ref' => $class->getCollection(),
'$id' => $class->getDatabaseIdentifierValue($id),
);

if ($referenceMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB) {
$dbRef['$db'] = $this->getDocumentDatabase($class->name)->getName();
switch ($referenceMapping['storeAs']) {
case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
if ($class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
throw MappingException::simpleReferenceMustNotTargetDiscriminatedDocument($referenceMapping['targetDocument']);
}

return $class->getDatabaseIdentifierValue($id);
break;


case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
$dbRef = ['id' => $class->getDatabaseIdentifierValue($id)];
break;

default:
$dbRef = [
'$ref' => $class->getCollection(),
'$id' => $class->getDatabaseIdentifierValue($id),
];
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
Expand Down
4 changes: 2 additions & 2 deletions lib/Doctrine/ODM/MongoDB/Hydrator/HydratorFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ private function generateHydratorClass(ClassMetadata $class, $hydratorClassName,
\$mongoId = \$reference;
} else {
\$className = \$this->unitOfWork->getClassNameForAssociation(\$this->class->fieldMappings['%2\$s'], \$reference);
\$mongoId = \$reference['\$id'];
\$mongoId = \$reference[ClassMetadataInfo::getReferencePrefix(\$this->class->fieldMappings['%2\$s']['storeAs']) . 'id'];
}
\$targetMetadata = \$this->dm->getClassMetadata(\$className);
\$id = \$targetMetadata->getPHPIdentifierValue(\$mongoId);
Expand Down Expand Up @@ -296,7 +296,7 @@ private function generateHydratorClass(ClassMetadata $class, $hydratorClassName,
\$className = \$mapping['targetDocument'];
\$targetClass = \$this->dm->getClassMetadata(\$mapping['targetDocument']);
\$mappedByMapping = \$targetClass->fieldMappings[\$mapping['mappedBy']];
\$mappedByFieldName = isset(\$mappedByMapping['storeAs']) && \$mappedByMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID ? \$mapping['mappedBy'] : \$mapping['mappedBy'].'.\$id';
\$mappedByFieldName = isset(\$mappedByMapping['storeAs']) && \$mappedByMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID ? \$mapping['mappedBy'] : \$mapping['mappedBy'].'.'.ClassMetadataInfo::getReferencePrefix(\$mappedByMapping['storeAs']).'id';
\$criteria = array_merge(
array(\$mappedByFieldName => \$data['_id']),
isset(\$this->class->fieldMappings['%2\$s']['criteria']) ? \$this->class->fieldMappings['%2\$s']['criteria'] : array()
Expand Down
24 changes: 24 additions & 0 deletions lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class ClassMetadataInfo implements \Doctrine\Common\Persistence\Mapping\ClassMet
const REFERENCE_STORE_AS_ID = 'id';
const REFERENCE_STORE_AS_DB_REF = 'dbRef';
const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
const REFERENCE_STORE_AS_REF = 'ref';

/* The inheritance mapping types */
/**
Expand Down Expand Up @@ -467,6 +468,29 @@ public function __construct($documentName)
$this->rootDocumentName = $documentName;
}

/**
* Helper method to get reference id of ref* type references
* @param array $reference
* @param string $storeAs
* @return mixed
* @internal
*/
public static function getReferenceId($reference, $storeAs)
{
return $reference[ClassMetadataInfo::getReferencePrefix($storeAs) . 'id'];
}

/**
* Returns the reference prefix used for a referene
* @param string $storeAs
* @return string
* @internal
*/
public static function getReferencePrefix($storeAs)
{
return $storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_REF ? '' : '$';
}

/**
* {@inheritDoc}
*/
Expand Down
36 changes: 23 additions & 13 deletions lib/Doctrine/ODM/MongoDB/Persisters/DocumentPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ private function loadReferenceManyCollectionOwningSide(PersistentCollectionInter
$mongoId = $reference;
} else {
$className = $this->uow->getClassNameForAssociation($mapping, $reference);
$mongoId = $reference['$id'];
$mongoId = ClassMetadataInfo::getReferenceId($reference, $mapping['storeAs']);
}
$id = $this->dm->getClassMetadata($className)->getPHPIdentifierValue($mongoId);

Expand Down Expand Up @@ -805,7 +805,12 @@ 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['storeAs']) && $mappedByMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID ? $mapping['mappedBy'] : $mapping['mappedBy'] . '.$id';
if (isset($mappedByMapping['storeAs']) && $mappedByMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
$mappedByFieldName = $mapping['mappedBy'];
} else {
$mappedByFieldName = $mapping['mappedBy'] . '.' . ClassMetadataInfo::getReferencePrefix(isset($mappedByMapping['storeAs']) ? $mappedByMapping['storeAs'] : null) . 'id';
}

$criteria = $this->cm->merge(
array($mappedByFieldName => $ownerClass->getIdentifierObject($owner)),
$this->dm->getFilterCollection()->getFilterCriteria($targetClass),
Expand Down Expand Up @@ -1177,7 +1182,7 @@ private function prepareQueryElement($fieldName, $value = null, $class = null, $

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

// Process targetDocument identifier fields
Expand Down Expand Up @@ -1424,20 +1429,25 @@ private function getWriteOptions(array $options = array())
private function prepareDbRefElement($fieldName, $value, array $mapping, $inNewObj)
{
$dbRef = $this->dm->createDBRef($value, $mapping);
if ($inNewObj) {
if ($inNewObj || $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
return [[$fieldName, $dbRef]];
}
$keys = ['$ref' => true, '$id' => true, '$db' => true];
if ($mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
unset($keys['$db']);
}
if (isset($mapping['targetDocument'])) {
unset($keys['$ref'], $keys['$db']);

if ($mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_REF) {
$keys = ['id' => true];
} else {
$keys = ['$ref' => true, '$id' => true, '$db' => true];

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

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

if ($mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
return [[$fieldName, $dbRef]];
} elseif ($mapping['type'] === 'many') {
if ($mapping['type'] === 'many') {
return [[$fieldName, ['$elemMatch' => array_intersect_key($dbRef, $keys)]]];
} else {
return array_map(
Expand Down
36 changes: 22 additions & 14 deletions lib/Doctrine/ODM/MongoDB/Query/Expr.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,22 @@ public function references($document)
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_REF) {
$keys = ['id' => true];
} else {
$keys = ['$ref' => true, '$id' => true, '$db' => true];

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

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

foreach ($keys as $key => $value) {
$this->query[$mapping['name'] . '.$' . $key] = $dbRef['$' . $key];
$this->query[$mapping['name'] . '.' . $key] = $dbRef[$key];
}
}
} else {
Expand All @@ -117,18 +121,22 @@ public function includesReferenceTo($document)
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_REF) {
$keys = ['id' => true];
} else {
$keys = ['$ref' => true, '$id' => true, '$db' => true];

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

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

foreach ($keys as $key => $value) {
$this->query[$mapping['name']]['$elemMatch']['$' . $key] = $dbRef['$' . $key];
$this->query[$mapping['name']]['$elemMatch'][$key] = $dbRef[$key];
}
}
} else {
Expand Down
3 changes: 2 additions & 1 deletion lib/Doctrine/ODM/MongoDB/Query/ReferencePrimer.php
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ private function addManyReferences(PersistentCollectionInterface $persistentColl
{
$mapping = $persistentCollection->getMapping();


if ($mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
$className = $mapping['targetDocument'];
$class = $this->dm->getClassMetadata($className);
Expand All @@ -264,7 +265,7 @@ private function addManyReferences(PersistentCollectionInterface $persistentColl
if ($mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
$id = $reference;
} else {
$id = $reference['$id'];
$id = $reference[ClassMetadataInfo::getReferencePrefix($mapping['storeAs']) . 'id'];
$className = $this->uow->getClassNameForAssociation($mapping, $reference);
$class = $this->dm->getClassMetadata($className);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/Doctrine/ODM/MongoDB/SchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ private function doGetDocumentIndexes($documentName, array &$visited)
if ($key == $fieldMapping['name']) {
$key = $fieldMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID
? $key
: $key . '.$id';
: $key . '.' . ClassMetadataInfo::getReferencePrefix($fieldMapping['storeAs']) . 'id';
}
$newKeys[$key] = $v;
}
Expand Down
Loading

0 comments on commit 51ad6f0

Please sign in to comment.