Created
March 14, 2014 16:42
-
-
Save zarathustra323/9551530 to your computer and use it in GitHub Desktop.
Doctrine Event Subscriber to Remove DBRefs of Deleted Documents
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Foo\BarBundle\Listener; | |
use Doctrine\Common\EventSubscriber; | |
use Doctrine\ODM\MongoDB\Events; | |
use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs; | |
use Doctrine\ODM\MongoDB\DocumentManager; | |
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory; | |
use Doctrine\ODM\MongoDB\Types\Type as MongoType; | |
class DoctrineDBRefRemoval implements EventSubscriber | |
{ | |
public function getSubscribedEvents() | |
{ | |
return array( | |
Events::preRemove, | |
); | |
} | |
public function preRemove(LifecycleEventArgs $eventArgs) | |
{ | |
// Get the Document Manager | |
$documentManager = $eventArgs->getDocumentManager(); | |
// Get the Class Metadata Factory | |
$metadataFactory = $documentManager->getMetadataFactory(); | |
// Get the information on the deleted document | |
$deleted = $this->getDeletedInfo($eventArgs->getDocument(), $metadataFactory); | |
// Get all metadata for classes mapped to this document manager | |
$allMetadata = $metadataFactory->getAllMetadata(); | |
foreach ($allMetadata as $classMetadata) { | |
// Skipp Superclasses and Embedded Documents | |
// @todo Support recursuve removal of references from Embedded Documents | |
if ($classMetadata->isMappedSuperclass || $classMetadata->isEmbeddedDocument) { | |
continue; | |
} | |
// Get all associations for the current document class | |
$associations = $classMetadata->getAssociationNames(); | |
foreach ($associations as $assocFieldName) { | |
// Get the target class name of the association | |
$assocClassName = $classMetadata->getAssociationTargetClass($assocFieldName); | |
if ($deleted['className'] == $assocClassName) { | |
// The deleted object's class matches the target class of the association | |
if ($classMetadata->isSingleValuedReference($assocFieldName)) { | |
// Is a ReferenceOne. Process removals | |
// @todo Support simple references | |
$this->removeSingleReference( | |
$classMetadata->getName(), | |
$documentManager, | |
$deleted, | |
$assocFieldName | |
); | |
} elseif ($classMetadata->isCollectionValuedReference($assocFieldName)) { | |
// Is a ReferenceMany. Process removals | |
// @todo Support simple references | |
$this->removeCollectionReference( | |
$classMetadata->getName(), | |
$documentManager, | |
$deleted, | |
$assocFieldName | |
); | |
} | |
} | |
} | |
} | |
} | |
public function getDeletedInfo($document, ClassMetadataFactory $metadataFactory) | |
{ | |
// Document object, class name and class metadata object for the document that's being deleted | |
$deleted = array( | |
'className' => get_class($document), | |
'metadata' => $metadataFactory->getMetadataFor(get_class($document)) | |
); | |
// ID field and value of the deleted document | |
$deleted['idField'] = $deleted['metadata']->getIdentifier(); | |
$deleted['idValue'] = $deleted['metadata']->getFieldValue($document, $deleted['idField']); | |
// Format the ID value using the appropriate Doctrine MongoDB Type | |
if (MongoType::hasType($deleted['metadata']->getTypeOfField($deleted['idField']))) { | |
$typeClass = MongoType::getType($deleted['metadata']->getTypeOfField($deleted['idField'])); | |
$deleted['idValue'] = $typeClass->convertToDatabaseValue($deleted['idValue']); | |
} else { | |
throw new Exception('The identifer of the deleted object must have a valid Doctrine field type'); | |
} | |
return $deleted; | |
} | |
public function removeSingleReference($targetClass, DocumentManager $documentManager, array $deleted, $assocFieldName) | |
{ | |
$documentManager->createQueryBuilder($targetClass) | |
->update() | |
->multiple(true) | |
->field($assocFieldName . '.$id')->equals($deleted['idValue']) | |
->field($assocFieldName)->unsetField() | |
->getQuery() | |
->execute(); | |
} | |
public function removeCollectionReference($targetClass, DocumentManager $documentManager, array $deleted, $assocFieldName) | |
{ | |
$cursor = $documentManager->createQueryBuilder($targetClass) | |
->find() | |
->hydrate(false) | |
->field($assocFieldName . '.$id')->equals($deleted['idValue']) | |
->getQuery() | |
->execute(); | |
foreach ($cursor as $doc) { | |
$newRefs = array(); | |
foreach ($doc[$assocFieldName] as $ref) { | |
if (array_key_exists('$id', $ref)) { | |
if ($ref['$id'] != $deleted['idValue']) { | |
// Only save references that aren't the deleted object | |
$newRefs[] = $ref; | |
} | |
} else { | |
$newRefs[] = $ref; | |
} | |
} | |
if (empty($newRefs)) { | |
// Unset the entire field, since references no longer exist | |
$documentManager->createQueryBuilder($targetClass) | |
->update() | |
->field('_id')->equals($doc['_id']) | |
->field($assocFieldName)->unsetField() | |
->getQuery() | |
->execute(); | |
} else { | |
// Replace the current array with the new references | |
$documentManager->createQueryBuilder($targetClass) | |
->update() | |
->field('_id')->equals($doc['_id']) | |
->field($assocFieldName)->set($newRefs) | |
->getQuery() | |
->execute(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment