Permalink
Browse files

[MODM-105] Adding much improved reference support. Added mappedBy and…

… inversedBy for better performing references instead of always storing references on both sides.
  • Loading branch information...
jwage committed Feb 21, 2011
1 parent 3bf5d44 commit d1bffbd6bb7b7b690e25f1314f1bf6b69f1e41a6
@@ -203,7 +203,7 @@ private function generateHydratorClass(ClassMetadata $class, $hydratorClassName,
$mapping['fieldName'],
Type::getType($mapping['type'])->closureToPHP()
);
- } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE) {
+ } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isOwningSide']) {
$code .= sprintf(<<<EOF
/** @ReferenceOne */
@@ -222,6 +222,40 @@ private function generateHydratorClass(ClassMetadata $class, $hydratorClassName,
$mapping['name'],
$mapping['fieldName']
);
+ } elseif ($mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isInverseSide']) {
+ if ($mapping['repositoryMethod']) {
+ $code .= sprintf(<<<EOF
+
+ \$className = \$this->class->fieldMappings['%2\$s']['targetDocument'];
+ \$return = \$this->dm->getRepository(\$className)->%3\$s();
+ \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
+ \$hydratedData['%2\$s'] = \$return;
+
+EOF
+ ,
+ $mapping['name'],
+ $mapping['fieldName'],
+ $mapping['repositoryMethod']
+ );
+ } else {
+ $code .= sprintf(<<<EOF
+
+ \$className = \$this->class->fieldMappings['%2\$s']['targetDocument'];
+ \$criteria = array_merge(
+ array(\$this->class->fieldMappings['%2\$s']['mappedBy'] . '.\$id' => \$data['_id']),
+ isset(\$this->class->fieldMappings['%2\$s']['criteria']) ? \$this->class->fieldMappings['%2\$s']['criteria'] : array()
+ );
+ \$sort = isset(\$this->class->fieldMappings['%2\$s']['sort']) ? \$this->class->fieldMappings['%2\$s']['sort'] : array();
+ \$return = \$this->unitOfWork->getDocumentPersister(\$className)->load(\$criteria, null, array(), 0, \$sort);
+ \$this->class->reflFields['%2\$s']->setValue(\$document, \$return);
+ \$hydratedData['%2\$s'] = \$return;
+
+EOF
+ ,
+ $mapping['name'],
+ $mapping['fieldName']
+ );
+ }
} elseif ($mapping['association'] === ClassMetadata::REFERENCE_MANY || $mapping['association'] === ClassMetadata::EMBED_MANY) {
$code .= sprintf(<<<EOF
@@ -974,6 +974,18 @@ public function mapField(array $mapping)
if (isset($mapping['lock'])) {
$this->setLockMapping($mapping);
}
+ $mapping['isOwningSide'] = true;
+ $mapping['isInverseSide'] = false;
+ if (isset($mapping['reference'])) {
+ if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
+ $mapping['isOwningSide'] = true;
+ $mapping['isInverseSide'] = false;
+ }
+ if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
+ $mapping['isInverseSide'] = true;
+ $mapping['isOwningSide'] = false;
+ }
+ }
$this->fieldMappings[$mapping['fieldName']] = $mapping;
return $mapping;
}
@@ -176,6 +176,13 @@ class Field extends Annotation
public $discriminatorField;
public $discriminatorMap;
public $cascade;
+ public $inversedBy;
+ public $mappedBy;
+ public $repositoryMethod;
+ public $sort = array();
+ public $criteria = array();
+ public $limit;
+ public $skip;
}
final class ReferenceMany extends Field
{
@@ -185,6 +192,13 @@ class Field extends Annotation
public $discriminatorField;
public $discriminatorMap;
public $cascade;
+ public $inversedBy;
+ public $mappedBy;
+ public $repositoryMethod;
+ public $sort = array();
+ public $criteria = array();
+ public $limit;
+ public $skip;
public $strategy = 'pushAll'; // pushAll, set
}
class NotSaved extends Field {}
@@ -156,7 +156,7 @@ private function changed()
{
if ( ! $this->isDirty) {
$this->isDirty = true;
- if ($this->dm && $this->mapping !== null && $this->dm->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
+ if ($this->dm && $this->mapping !== null && $this->mapping['isOwningSide'] && $this->dm->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
$this->uow->scheduleForDirtyCheck($this->owner);
}
}
@@ -408,6 +408,9 @@ public function getValues()
*/
public function count()
{
+ if ($this->mapping['isInverseSide']) {
+ $this->initialize();
+ }
return count($this->mongoData) + $this->coll->count();
}
@@ -508,9 +511,11 @@ public function clear()
}
$this->mongoData = array();
$this->coll->clear();
- $this->changed();
- $this->uow->scheduleCollectionDeletion($this);
- $this->takeSnapshot();
+ if ($this->mapping['isOwningSide']) {
+ $this->changed();
+ $this->uow->scheduleCollectionDeletion($this);
+ $this->takeSnapshot();
+ }
}
/**
@@ -88,6 +88,10 @@ public function __construct(DocumentManager $dm, PersistenceBuilder $pb, UnitOfW
*/
public function delete(PersistentCollection $coll, array $options)
{
+ $mapping = $coll->getMapping();
+ if ($mapping['isInverseSide']) {
+ return; // ignore inverse side
+ }
list($propertyPath, $parent) = $this->getPathAndParent($coll);
$query = array($this->cmd . 'unset' => array($propertyPath => true));
$this->executeQuery($parent, $query, $options);
@@ -101,6 +105,10 @@ public function delete(PersistentCollection $coll, array $options)
*/
public function update(PersistentCollection $coll, array $options)
{
+ $mapping = $coll->getMapping();
+ if ($mapping['isInverseSide']) {
+ return; // ignore inverse side
+ }
$this->deleteRows($coll, $options);
$this->insertRows($coll, $options);
}
@@ -148,6 +148,16 @@ public function __construct(PersistenceBuilder $pb, DocumentManager $dm, EventMa
$this->collection = $dm->getDocumentCollection($class->name);
}
+ public function getInserts()
+ {
+ return $this->queuedInserts;
+ }
+
+ public function isQueuedForInsert($document)
+ {
+ return isset($this->queuedInserts[spl_object_hash($document)]) ? true : false;
+ }
+
/**
* Adds a document to the queued insertions.
* The document remains queued until {@link executeInserts} is invoked.
@@ -324,13 +334,18 @@ public function refresh($id, $document)
* a new document is created.
* @param array $hints Hints for document creation.
* @param int $lockMode
+ * @param array $sort
* @return object The loaded and managed document instance or NULL if the document can not be found.
* @todo Check identity map? loadById method? Try to guess whether $criteria is the id?
*/
- public function load($criteria, $document = null, array $hints = array(), $lockMode = 0)
+ public function load($criteria, $document = null, array $hints = array(), $lockMode = 0, array $sort = array())
{
$criteria = $this->prepareQuery($criteria);
- $result = $this->collection->findOne($criteria);
+ $cursor = $this->collection->find($criteria)->limit(1);
+ if ($sort) {
+ $cursor->sort($sort);
+ }
+ $result = $cursor->getSingleResult();
if ($this->class->isLockable) {
$lockMapping = $this->class->fieldMappings[$this->class->lockField];
@@ -457,7 +472,16 @@ public function loadCollection(PersistentCollection $collection)
break;
case ClassMetadata::REFERENCE_MANY:
- $this->loadReferenceManyCollection($collection);
+ $mapping = $collection->getMapping();
+ if ($mapping['repositoryMethod']) {
+ $this->loadReferenceManyWithRepositoryMethod($collection);
+ } else {
+ if ($mapping['isOwningSide']) {
+ $this->loadReferenceManyCollectionOwningSide($collection);
+ } else {
+ $this->loadReferenceManyCollectionInverseSide($collection);
+ }
+ }
break;
}
}
@@ -481,7 +505,7 @@ private function loadEmbedManyCollection(PersistentCollection $collection)
}
}
- private function loadReferenceManyCollection(PersistentCollection $collection)
+ private function loadReferenceManyCollectionOwningSide(PersistentCollection $collection)
{
$mapping = $collection->getMapping();
$cmd = $this->cmd;
@@ -502,15 +526,76 @@ private function loadReferenceManyCollection(PersistentCollection $collection)
foreach ($groupedIds as $className => $ids) {
$class = $this->dm->getClassMetadata($className);
$mongoCollection = $this->dm->getDocumentCollection($className);
- $data = $mongoCollection->find(array('_id' => array($cmd . 'in' => $ids)));
- foreach ($data as $documentData) {
+ $criteria = array_merge(
+ array('_id' => array($cmd . 'in' => $ids)),
+ $mapping['criteria']
+ );
+ $cursor = $mongoCollection->find($criteria);
+ if ($mapping['sort']) {
+ $cursor->sort($mapping['sort']);
+ }
+ if ($mapping['limit']) {
+ $cursor->limit($mapping['limit']);
+ }
+ if ($mapping['skip']) {
+ $cursor->skip($mapping['skip']);
+ }
+ foreach ($cursor as $documentData) {
$document = $this->uow->getById((string) $documentData['_id'], $class->rootDocumentName);
$data = $this->hydratorFactory->hydrate($document, $documentData);
$this->uow->setOriginalDocumentData($document, $data);
}
}
}
+ private function loadReferenceManyCollectionInverseSide(PersistentCollection $collection)
+ {
+ $mapping = $collection->getMapping();
+ $owner = $collection->getOwner();
+ $class = $this->dm->getClassMetadata($mapping['targetDocument']);
+ $mongoCollection = $this->dm->getDocumentCollection($mapping['targetDocument']);
+ $criteria = array_merge(
+ array($mapping['mappedBy'].'.'.$this->cmd.'id' => $class->getIdentifierObject($owner)),
+ $mapping['criteria']
+ );
+ $cursor = $mongoCollection->find($criteria);
+ if ($mapping['sort']) {
+ $cursor->sort($mapping['sort']);
+ }
+ if ($mapping['limit']) {
+ $cursor->limit($mapping['limit']);
+ }
+ if ($mapping['skip']) {
+ $cursor->skip($mapping['skip']);
+ }
+ foreach ($cursor as $documentData) {
+ $document = $this->dm->getReference($class->name, (string) $documentData['_id']);

This comment has been minimized.

Show comment
Hide comment
@tecbot

tecbot Feb 24, 2011

Contributor

the discriminator map for inverse side, doesn't work.

@tecbot

tecbot Feb 24, 2011

Contributor

the discriminator map for inverse side, doesn't work.

This comment has been minimized.

Show comment
Hide comment
@jwage

jwage Feb 24, 2011

Member

If you think about it, the inverse side discriminator map cannot work. How could it? we no longer store references on the inverse side if you specify mappedBy

@jwage

jwage Feb 24, 2011

Member

If you think about it, the inverse side discriminator map cannot work. How could it? we no longer store references on the inverse side if you specify mappedBy

This comment has been minimized.

Show comment
Hide comment
@tecbot

tecbot Feb 24, 2011

Contributor

i mean the discriminator map for the class and this is stored in the document, but the returned document from doctrine is only the document wich declare the discriminator map, because it use only the targetDocument option.

@tecbot

tecbot Feb 24, 2011

Contributor

i mean the discriminator map for the class and this is stored in the document, but the returned document from doctrine is only the document wich declare the discriminator map, because it use only the targetDocument option.

This comment has been minimized.

Show comment
Hide comment
@jwage

jwage Feb 24, 2011

Member

But like I said, it is impossible to discriminate which class to use because we do not store a reference in the database for the inverse side, so we have no where to store the discriminator value, which is used to lookup what class to instantiate. If you want to use inverse and owning side you cannot use the discriminator map on the inverse side.

@jwage

jwage Feb 24, 2011

Member

But like I said, it is impossible to discriminate which class to use because we do not store a reference in the database for the inverse side, so we have no where to store the discriminator value, which is used to lookup what class to instantiate. If you want to use inverse and owning side you cannot use the discriminator map on the inverse side.

This comment has been minimized.

Show comment
Hide comment
@tecbot

tecbot Feb 24, 2011

Contributor

I think you dont understand me.

When i load a document with find and the class has a discriminator map. e.g:

@mongodb:Document()
@mongodb:InheritanceType("SINGLE_COLLECTION")
@mongodb:DiscriminatorMap({
    "foo" = "Bar",
    "bar" = "Foo"
})
class Foo
{
     // ....
}

Then doctrine return the right class Bar or Foo for a id.

But wenn the id loaded with the loadReferenceManyCollectionInverseSide function, then is the class only Foo.

@tecbot

tecbot Feb 24, 2011

Contributor

I think you dont understand me.

When i load a document with find and the class has a discriminator map. e.g:

@mongodb:Document()
@mongodb:InheritanceType("SINGLE_COLLECTION")
@mongodb:DiscriminatorMap({
    "foo" = "Bar",
    "bar" = "Foo"
})
class Foo
{
     // ....
}

Then doctrine return the right class Bar or Foo for a id.

But wenn the id loaded with the loadReferenceManyCollectionInverseSide function, then is the class only Foo.

This comment has been minimized.

Show comment
Hide comment
@jwage

jwage Feb 24, 2011

Member

I understand now. I thought you meant the discriminator map on the reference. This is why test cases are important! :)

@jwage

jwage Feb 24, 2011

Member

I understand now. I thought you meant the discriminator map on the reference. This is why test cases are important! :)

This comment has been minimized.

Show comment
Hide comment
@tecbot

tecbot Feb 24, 2011

Contributor

hehe thats right ;).

@tecbot

tecbot Feb 24, 2011

Contributor

hehe thats right ;).

This comment has been minimized.

Show comment
Hide comment
@jwage

jwage Feb 24, 2011

Member

8bb7d01

I think this should take care of it.

@jwage

jwage Feb 24, 2011

Member

8bb7d01

I think this should take care of it.

+ if ($document instanceof Proxy && ! $document->__isInitialized__) {
+ $data = $this->hydratorFactory->hydrate($document, $documentData);
+ $this->uow->setOriginalDocumentData($document, $data);
+ }
+ $collection->add($document);
+ }
+ }
+
+ private function loadReferenceManyWithRepositoryMethod(PersistentCollection $collection)
+ {
+ $mapping = $collection->getMapping();
+ $cursor = $this->dm->getRepository($mapping['targetDocument'])->$mapping['repositoryMethod']();
+ if ($mapping['sort']) {
+ $cursor->sort($mapping['sort']);
+ }
+ if ($mapping['limit']) {
+ $cursor->limit($mapping['limit']);
+ }
+ if ($mapping['skip']) {
+ $cursor->skip($mapping['skip']);
+ }
+ foreach ($cursor as $document) {
+ $collection->add($document);
+ }
+ }
+
/**
* Prepares a query array by converting the portable Doctrine types to the types mongodb expects.
*
@@ -109,8 +109,13 @@ public function prepareInsertData($document)
// @ReferenceOne
} elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) {
+ if ($mapping['isInverseSide']) {
+ continue;
+ }
+
$oid = spl_object_hash($new);
- if (isset($this->queuedInserts[$oid]) || $this->uow->isScheduledForInsert($new)) {
+
+ if ($this->isScheduledForInsert($new)) {
// The associated document $new is not yet persisted, so we must
// set $new = null, in order to insert a null value and schedule an
// extra update on the UnitOfWork.
@@ -230,7 +235,7 @@ public function prepareUpdateData($document)
}
// @ReferenceOne
- } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) {
+ } elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE && $mapping['isOwningSide']) {
if (isset($new) || $mapping['nullable'] === true) {
$updateData[$this->cmd . 'set'][$mapping['name']] = $this->prepareReferencedDocumentValue($mapping, $new);
} else {
@@ -323,4 +328,10 @@ public function prepareEmbeddedDocumentValue(array $embeddedMapping, $embeddedDo
}
return $embeddedDocumentValue;
}
+
+ private function isScheduledForInsert($document)
+ {
+ return $this->uow->isScheduledForInsert($document)
+ || $this->uow->getDocumentPersister(get_class($document))->isQueuedForInsert($document);
+ }
}
Oops, something went wrong.

0 comments on commit d1bffbd

Please sign in to comment.