Doctrine Event Subscriber to Remove DBRefs of Deleted Documents
<?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