Skip to content

Instantly share code, notes, and snippets.

@zarathustra323
Created March 14, 2014 16:42
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save zarathustra323/9551530 to your computer and use it in GitHub Desktop.
Save zarathustra323/9551530 to your computer and use it in GitHub Desktop.
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