Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

first commit

  • Loading branch information...
commit 932c8e5d1cdacf1a31628f77380bcfc9f8ddd8ab 0 parents
@jwage jwage authored
Showing with 1,925 additions and 0 deletions.
  1. +9 −0 .gitmodules
  2. +114 −0 README.markdown
  3. +60 −0 lib/Doctrine/ODM/MongoDB/SoftDelete/Configuration.php
  4. +72 −0 lib/Doctrine/ODM/MongoDB/SoftDelete/Event/LifecycleEventArgs.php
  5. +59 −0 lib/Doctrine/ODM/MongoDB/SoftDelete/Events.php
  6. +197 −0 lib/Doctrine/ODM/MongoDB/SoftDelete/Persister.php
  7. +183 −0 lib/Doctrine/ODM/MongoDB/SoftDelete/SoftDeleteManager.php
  8. +48 −0 lib/Doctrine/ODM/MongoDB/SoftDelete/SoftDeleteable.php
  9. +350 −0 lib/Doctrine/ODM/MongoDB/SoftDelete/UnitOfWork.php
  10. +1 −0  lib/vendor/doctrine-common
  11. +1 −0  lib/vendor/doctrine-mongodb
  12. +1 −0  lib/vendor/doctrine-mongodb-odm
  13. +18 −0 phpunit.xml.dist
  14. +22 −0 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/ConfigurationTest.php
  15. +267 −0 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/FunctionalTest.php
  16. +151 −0 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/PersisterTest.php
  17. +34 −0 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/SoftDeletableTest.php
  18. +179 −0 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/SoftDeleteManagerTest.php
  19. +139 −0 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/UnitOfWorkTest.php
  20. +20 −0 tests/bootstrap.php
9 .gitmodules
@@ -0,0 +1,9 @@
+[submodule "lib/vendor/doctrine-mongodb-odm"]
+ path = lib/vendor/doctrine-mongodb-odm
+ url = git://github.com/doctrine/mongodb-odm.git
+[submodule "lib/vendor/doctrine-common"]
+ path = lib/vendor/doctrine-common
+ url = git://github.com/doctrine/common.git
+[submodule "lib/vendor/doctrine-mongodb"]
+ path = lib/vendor/doctrine-mongodb
+ url = git://github.com/doctrine/mongodb.git
114 README.markdown
@@ -0,0 +1,114 @@
+# Doctrine MongoDB ODM SoftDelete Functionality
+
+This library gives you some additional classes and API for managing the soft deleted state of Doctrine
+MongoDB ODM documents. To get started you just need to configure a few objects and get a SoftDeleteManager
+instance:
+
+## Setup
+
+ use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+ use Doctrine\ODM\MongoDB\SoftDelete\UnitOfWork;
+ use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteManager;
+ use Doctrine\Common\EventManager;
+
+ // $dm is a DocumentManager instance we should already have
+
+ $config = new Configuration();
+ $uow = new UnitOfWork($dm, $config);
+ $evm = new EventManager();
+ $sdm = new SoftDeleteManager($dm, $config, $uow, $evm);
+
+## SoftDelete Documents
+
+In order for your documents to work with the SoftDelete functionality they must implement the
+SoftDeleteable interface:
+
+ interface SoftDeleteable
+ {
+ function getDeletedAt();
+ function isDeleted();
+ }
+
+An implementation might look like this:
+
+ use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteable;
+
+ /** @mongodb:Document */
+ class User implements SoftDeleteable
+ {
+ // ...
+
+ /** @mongodb:Date */
+ private $deletedAt;
+
+ public function getDeletedAt()
+ {
+ return $this->deletedAt;
+ }
+
+ public function isDeleted()
+ {
+ return $this->deletedAt !== null ? true : false;
+ }
+
+ // ...
+ }
+
+## Managing Soft Delete State
+
+Once you have the $sdm you can start managing the soft delete state of your documents:
+
+ $jwage = $dm->getRepository('User')->findOneByUsername('jwage');
+ $fabpot = $dm->getRepository('User')->findOneByUsername('fabpot');
+ $sdm->delete($jwage);
+ $sdm->delete($fabpot);
+ $sdm->flush();
+
+The above would issue a simple query setting the deleted date:
+
+ db.users.update({ _id : { $in : userIds }}, { $set : { deletedAt : new Date() } })
+
+Now if we were to restore the documents:
+
+ $sdm->restore($jwage);
+ $sdm->flush();
+
+It would unset the deletedAt date:
+
+ db.users.update({ _id : { $in : userIds }}, { $unset : { deletedAt : true } })
+
+## Events
+
+We trigger some additional event lifecycle events when documents are soft deleted or restored:
+
+* Events::preSoftDelete
+* Events::postSoftDelete
+* Events::preSoftDeleteRestore
+* Events::postSoftDeleteRestore
+
+Using the events is easy, just define a class like the following:
+
+ class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
+ {
+ public function preSoftDelete(LifecycleEventArgs $args)
+ {
+ $document = $args->getDocument();
+ $sdm = $args->getSoftDeleteManager();
+ }
+
+ public function getSubscribedEvents()
+ {
+ return array(Events::preSoftDelete);
+ }
+ }
+
+Now we just need to add the event subscriber to the EventManager:
+
+ $eventSubscriber = new TestEventSubscriber();
+ $evm->addEventSubscriber($eventSubscriber);
+
+When we soft delete something the preSoftDelete() method will be invoked before any queries are sent
+to the database:
+
+ $sdm->delete($fabpot);
+ $sdm->flush();
60 lib/Doctrine/ODM/MongoDB/SoftDelete/Configuration.php
@@ -0,0 +1,60 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\SoftDelete;
+
+/**
+ * Configuration class for the SoftDelete functionality.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Configuration
+{
+ /**
+ * Array of configurable attributes.
+ *
+ * @var array
+ */
+ private $attributes = array(
+ 'deletedFieldName' => 'deletedAt'
+ );
+
+ /**
+ * Sets the name of the field to use to store the deleted at date.
+ *
+ * @param string $deletedFieldName
+ */
+ public function setDeletedFieldName($deletedFieldName)
+ {
+ $this->attributes['deletedFieldName'] = $deletedFieldName;
+ }
+
+ /**
+ * Gets the name of the field used to store the deleted at date.
+ *
+ * @return string $deletedFieldName
+ */
+ public function getDeletedFieldName()
+ {
+ return isset($this->attributes['deletedFieldName']) ? $this->attributes['deletedFieldName'] : 'deleted';
+ }
+}
72 lib/Doctrine/ODM/MongoDB/SoftDelete/Event/LifecycleEventArgs.php
@@ -0,0 +1,72 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+*/
+
+namespace Doctrine\ODM\MongoDB\SoftDelete\Event;
+
+use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteable;
+use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteManager;
+use Doctrine\Common\EventArgs;
+
+/**
+ * Lifecycle Events are triggered by the UnitOfWork during lifecycle transitions
+ * of documents.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class LifecycleEventArgs extends EventArgs
+{
+ /**
+ * @var SoftDeleteManager $sdm
+ */
+ private $sdm;
+
+ /**
+ * @var SoftDeleteable $document
+ */
+ private $document;
+
+ public function __construct(SoftDeleteable $document, SoftDeleteManager $sdm)
+ {
+ $this->document = $document;
+ $this->sdm = $sdm;
+ }
+
+ /**
+ * Gets the SoftDeletable document instance.
+ *
+ * @return SoftDeleteable $document
+ */
+ public function getDocument()
+ {
+ return $this->document;
+ }
+
+ /**
+ * Gets the SoftDeleteManager
+ *
+ * @return SoftDeleteManager
+ */
+ public function getSoftDeleteManager()
+ {
+ return $this->sdm;
+ }
+}
59 lib/Doctrine/ODM/MongoDB/SoftDelete/Events.php
@@ -0,0 +1,59 @@
+<?php
+/*
+ * $Id$
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\SoftDelete;
+
+/**
+ * Container for all SoftDelete events.
+ *
+ * This class cannot be instantiated.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+final class Events
+{
+ private function __construct() {}
+
+ /**
+ * Invoked before a document is soft deleted in the database.
+ */
+ const preSoftDelete = 'preSoftDelete';
+
+ /**
+ * Invokes after a document is soft deleted in the database.
+ */
+ const postSoftDelete = 'postSoftDelete';
+
+
+ /**
+ * Invoked before a documents soft delete is removed and document restored in the database.
+ */
+ const preSoftDeleteRestore = 'preSoftDeleteRestore';
+
+ /**
+ * Invoked after a documents soft delete is removed and document restored in the database.
+ */
+ const postSoftDeleteRestore = 'postSoftDeleteRestore';
+
+}
197 lib/Doctrine/ODM/MongoDB/SoftDelete/Persister.php
@@ -0,0 +1,197 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\SoftDelete;
+
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
+use Doctrine\MongoDB\Collection;
+use MongoDate;
+
+/**
+ * The Persister class is responsible for persisting the queued deletions and restorations.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class Persister
+{
+ /**
+ * ClassMetadata instance.
+ *
+ * @var Doctrine\MongoDB\ODM\Mapping\ClassMetadata $class
+ */
+ private $class;
+
+ /**
+ * Database Collection instance.
+ *
+ * @var Doctrine\MongoDB\Collection $collection
+ */
+ private $collection;
+
+ /**
+ * Array of queued documents to be deleted.
+ *
+ * @var array
+ */
+ private $queuedDeletes = array();
+
+ /**
+ * Array of queued documents to be restored.
+ *
+ * @var array
+ */
+ private $queuedRestores = array();
+
+ /**
+ * Constructs a new Persister instance
+ *
+ * @param Configuration $configuration
+ * @param ClassMetadata $class
+ * @param Collection $collection
+ */
+ public function __construct(Configuration $configuration, ClassMetadata $class, Collection $collection)
+ {
+ $this->configuration = $configuration;
+ $this->class = $class;
+ $this->collection = $collection;
+ }
+
+ /**
+ * Gets the ClassMetadata instance.
+ *
+ * @return ClassMetadata $class
+ */
+ public function getClass()
+ {
+ return $this->class;
+ }
+
+ /**
+ * Gets the database Collection instance.
+ *
+ * @return Collection $collection
+ */
+ public function getCollection()
+ {
+ return $this->collection;
+ }
+
+ /**
+ * Add a SoftDeleteable document to the queued deletes.
+ *
+ * @param SoftDeleteable $document
+ */
+ public function addDelete(SoftDeleteable $document)
+ {
+ $this->queuedDeletes[spl_object_hash($document)] = $document;
+ }
+
+ /**
+ * Gets the array of SoftDeleteable documents queued for deletion.
+ *
+ * @return array $queuedDeletes
+ */
+ public function getDeletes()
+ {
+ return $this->queuedDeletes;
+ }
+
+ /**
+ * Add a SoftDeleteable document to the queued restores.
+ *
+ * @param SoftDeleteable $document
+ */
+ public function addRestore(SoftDeleteable $document)
+ {
+ $this->queuedRestores[spl_object_hash($document)] = $document;
+ }
+
+ /**
+ * Gets the array of SoftDeleteable documents queued for restoration.
+ *
+ * @return array $queuedRestores
+ */
+ public function getRestores()
+ {
+ return $this->queuedRestores;
+ }
+
+ /**
+ * Executes the queued deletes.
+ *
+ * @param MongoDate $date Date to the deleted field to. Mainly for testing.
+ */
+ public function executeDeletes(MongoDate $date = null)
+ {
+ $ids = array();
+ foreach ($this->queuedDeletes as $document) {
+ $ids[] = $this->class->getIdentifierObject($document);
+ }
+
+ $query = array(
+ '_id' => array(
+ '$in' => $ids
+ )
+ );
+ $newObj = array(
+ '$set' => array(
+ $this->configuration->getDeletedFieldName() => $date ? $date : new MongoDate()
+ )
+ );
+
+ $this->collection->update($query, $newObj, array(
+ 'multiple' => true,
+ 'safe' => true
+ ));
+
+ $this->queuedDeletes = array();
+ }
+
+ /**
+ * Executes the queued restores.
+ */
+ public function executeRestores()
+ {
+ $ids = array();
+ foreach ($this->queuedRestores as $document) {
+ $ids[] = $this->class->getIdentifierObject($document);
+ }
+
+ $query = array(
+ '_id' => array(
+ '$in' => $ids
+ )
+ );
+ $newObj = array(
+ '$unset' => array(
+ $this->configuration->getDeletedFieldName() => true
+ )
+ );
+ $this->collection->update($query, $newObj, array(
+ 'multiple' => true,
+ 'safe' => true
+ ));
+
+ $this->queuedRestores = array();
+ }
+}
183 lib/Doctrine/ODM/MongoDB/SoftDelete/SoftDeleteManager.php
@@ -0,0 +1,183 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\SoftDelete;
+
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\Common\EventManager;
+
+/**
+ * The SoftDeleteManager class is responsible for managing the deleted state of a SoftDeleteable instance.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class SoftDeleteManager
+{
+ /**
+ * DocumentManager instance this object wraps.
+ *
+ * @var DocumentManager $dm
+ */
+ private $dm;
+
+ /**
+ * The SoftDelete Configuration instance.
+ *
+ * @var Configuration $config
+ */
+ private $config;
+
+ /**
+ * The SoftDelete UnitOfWork instance.
+ *
+ * @var UnitOfWork $unitOfWork
+ */
+ private $unitOfWork;
+
+ /**
+ * The EventManager instance used for managing events.
+ *
+ * @var EventManager $eventManager
+ */
+ private $eventManager;
+
+ /**
+ * Constructs a new SoftDeleteManager instance.
+ *
+ * @param DocumentManager $dm
+ * @param Configuration $configuration
+ * @param UnitOfWork $unitOfWork
+ */
+ public function __construct(DocumentManager $dm, Configuration $configuration, UnitOfWork $unitOfWork, EventManager $eventManager = null)
+ {
+ $this->dm = $dm;
+ $this->config = $configuration;
+ $this->unitOfWork = $unitOfWork;
+ $this->eventManager = $eventManager ?: new EventManager();
+ $this->unitOfWork->setEventManager($this->eventManager);
+ $this->unitOfWork->setSoftDeleteManager($this);
+ }
+
+ /**
+ * Gets the DocumentManager
+ *
+ * @return DocumentManager $dm
+ */
+ public function getDocumentManager()
+ {
+ return $this->dm;
+ }
+
+ /**
+ * Gets the Configuration
+ *
+ * @return Configuration $config
+ */
+ public function getConfiguration()
+ {
+ return $this->config;
+ }
+
+ /**
+ * Gets the UnitOfWork
+ *
+ * @return UnitOfWork $unitOfWork
+ */
+ public function getUnitOfWork()
+ {
+ return $this->unitOfWork;
+ }
+
+ /**
+ * Gets the EventManager
+ *
+ * @return EventManager $eventManager
+ */
+ public function getEventManager()
+ {
+ return $this->eventManager;
+ }
+
+ /**
+ * Creates a new query builder instance that will automatically exclude deleted documents
+ * by adding a { deletedAt : { $exists : false } } condition.
+ *
+ * @param string $documentName The document class name to create the query builder for.
+ * @return Doctrine\MongoDB\ODM\Query\Builder $qb
+ */
+ public function createQueryBuilder($documentName = null)
+ {
+ return $this->dm->createQueryBuilder($documentName)
+ ->field($this->config->getDeletedFieldName())
+ ->exists(false);
+ }
+
+ /**
+ * Creates a new query builder instance that will return only deleted documents
+ * by adding a { deletedAt : { $exists : true } } condition.
+ *
+ * @param string $documentName The document class name to create the query builder for.
+ * @return Doctrine\MongoDB\ODM\Query\Builder $qb
+ */
+ public function createDeletedQueryBuilder($documentName = null)
+ {
+ return $this->dm->createQueryBuilder($documentName)
+ ->field($this->config->getDeletedFieldName())
+ ->exists(true);
+ }
+
+ /**
+ * Schedules a SoftDeleteable document for soft deletion on next flush().
+ *
+ * @param SoftDeleteable $document
+ */
+ public function delete(SoftDeleteable $document)
+ {
+ $this->unitOfWork->delete($document);
+ }
+
+ /**
+ * Schedulds a SoftDeleteable document for soft delete restoration on next flush().
+ *
+ * @param SoftDeleteable $document
+ */
+ public function restore(SoftDeleteable $document)
+ {
+ $this->unitOfWork->restore($document);
+ }
+
+ /**
+ * Flushes all scheduled deletions and restorations to the database.
+ */
+ public function flush()
+ {
+ $this->unitOfWork->commit();
+ }
+
+ /**
+ * Clears the UnitOfWork and erases any currently scheduled deletions or restorations.
+ */
+ public function clear()
+ {
+ $this->unitOfWork->clear();
+ }
+}
48 lib/Doctrine/ODM/MongoDB/SoftDelete/SoftDeleteable.php
@@ -0,0 +1,48 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\SoftDelete;
+
+use Doctrine\ODM\MongoDB\DocumentManager;
+
+/**
+ * Interface for Doctrine MongoDB ODM documents to implement if they want to use
+ * the SoftDelete functionality.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+interface SoftDeleteable
+{
+ /**
+ * Gets the date that this object was deleted at.
+ *
+ * @return DateTime $deletedAt
+ */
+ function getDeletedAt();
+
+ /**
+ * Checks whether this object is deleted or not.
+ *
+ * @return boolean $isDeleted
+ */
+ function isDeleted();
+}
350 lib/Doctrine/ODM/MongoDB/SoftDelete/UnitOfWork.php
@@ -0,0 +1,350 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ODM\MongoDB\SoftDelete;
+
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+use Doctrine\Common\EventManager;
+use InvalidArgumentException;
+use DateTime;
+
+/**
+ * UnitOfWork is responsible for tracking the deleted state of objects and giving you the ability
+ * to queue deletions and restorations to be committed.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link www.doctrine-project.com
+ * @since 1.0
+ * @author Jonathan H. Wage <jonwage@gmail.com>
+ */
+class UnitOfWork
+{
+ /**
+ * The DocumentManager instance.
+ *
+ * @var DocumentManager $dm
+ */
+ private $dm;
+
+ /**
+ * The SoftDeleteManager instance.
+ *
+ * @var string
+ */
+ private $sdm;
+
+ /**
+ * The SoftDelete configuration instance/
+ *
+ * @var Configuration $configuration
+ */
+ private $configuration;
+
+ /**
+ * The EventManager instance used for managing events.
+ *
+ * @var EventManager $eventManager
+ */
+ private $eventManager;
+
+ /**
+ * Array of scheduled document deletes.
+ *
+ * @var array
+ */
+ private $documentDeletes = array();
+
+ /**
+ * Array of scheduled document restores.
+ *
+ * @var array
+ */
+ private $documentRestores = array();
+
+ /**
+ * Array of lazily instantiated soft delete document persisters.
+ *
+ * @var string
+ */
+ private $persisters = array();
+
+ /**
+ * Constructs a new UnitOfWork instance.
+ *
+ * @param DocumentManager $dm
+ * @param Configuration $configuration
+ */
+ public function __construct(DocumentManager $dm, Configuration $configuration)
+ {
+ $this->dm = $dm;
+ $this->configuration = $configuration;
+ }
+
+ /**
+ * Sets the SoftDeleteManager this UnitOfWork belongs to.
+ *
+ * @param SoftDeleteManager $sdm
+ */
+ public function setSoftDeleteManager(SoftDeleteManager $sdm)
+ {
+ $this->sdm = $sdm;
+ }
+
+ /**
+ * Gets the SoftDeleteManager this UnitOfWork belongs to.
+ *
+ * @return SoftDeleteManager $sdm
+ */
+ public function getSoftDeleteManager()
+ {
+ return $this->sdm;
+ }
+
+ /**
+ * Sets the UnitOfWork event manager.
+ *
+ * @param EventManager $eventManager
+ */
+ public function setEventManager(EventManager $eventManager)
+ {
+ $this->eventManager = $eventManager;
+ }
+
+ /**
+ * Gets the UnitOfWork event manager.
+ *
+ * @return EventManager $eventManager
+ */
+ public function getEventManager()
+ {
+ return $this->eventManager;
+ }
+
+ /**
+ * Gets the DocumentManager instance
+ *
+ * @return DocumentManager $dm
+ */
+ public function getDocumentManager()
+ {
+ return $this->dm;
+ }
+
+ /**
+ * Gets the array of scheduled document deletes.
+ *
+ * @return array $documentDeletes
+ */
+ public function getDocumentDeletes()
+ {
+ return $this->documentDeletes;
+ }
+
+ /**
+ * Gets the array of scheduled document restores.
+ *
+ * @return array $documentRestores
+ */
+ public function getDocumentRestores()
+ {
+ return $this->documentRestores;
+ }
+
+ /**
+ * Checks if a given SoftDeleteable document instance is currently scheduled for delete.
+ *
+ * @param SoftDeleteable $document
+ */
+ public function isScheduledForDelete(SoftDeleteable $document)
+ {
+ return isset($this->documentDeletes[spl_object_hash($document)]) ? true : false;
+ }
+
+ /**
+ * Checks if a given SoftDeleteable document instance is currently scheduled for restore.
+ *
+ * @param SoftDeleteable $document
+ */
+ public function isScheduledForRestore(SoftDeleteable $document)
+ {
+ return isset($this->documentRestores[spl_object_hash($document)]) ? true : false;
+ }
+
+ /**
+ * Gets or creates a Persister instance for the given class name.
+ *
+ * @param string $className
+ * @return Persister $persister
+ */
+ public function getDocumentPersister($className)
+ {
+ if (isset($this->persisters[$className])) {
+ return $this->persisters[$className];
+ }
+ $class = $this->dm->getClassMetadata($className);
+ $collection = $this->dm->getDocumentCollection($className);
+ $this->persisters[$className] = new Persister($this->configuration, $class, $collection);
+ return $this->persisters[$className];
+ }
+
+ /**
+ * Schedules a SoftDeleteable document instance for deletion on next flush.
+ *
+ * @param SoftDeleteable $document
+ * @throws InvalidArgumentException
+ */
+ public function delete(SoftDeleteable $document)
+ {
+ if ($document->isDeleted() === true) {
+ throw new InvalidArgumentException('Document is already deleted.');
+ }
+
+ $oid = spl_object_hash($document);
+ if (isset($this->documentDeletes[$oid])) {
+ throw new InvalidArgumentException('Document is already scheduled for delete.');
+ }
+
+ // If scheduled for restore then remove it
+ unset($this->documentRestores[$oid]);
+
+ $this->documentDeletes[$oid] = $document;
+ }
+
+ /**
+ * Schedules a SoftDeleteable document instance for restoration on next flush.
+ *
+ * @param SoftDeleteable $document
+ * @throws InvalidArgumentException
+ */
+ public function restore(SoftDeleteable $document)
+ {
+ if ($document->isDeleted() === false) {
+ throw new InvalidArgumentException('Document must be already deleted or scheduled for delete in order to be restored.');
+ }
+
+ $oid = spl_object_hash($document);
+ if (isset($this->documentRestores[$oid])) {
+ throw new InvalidArgumentException('Document is already scheduled for restore.');
+ }
+
+ // If scheduled for delete then remove it
+ unset($this->documentDeletes[$oid]);
+
+ $this->documentRestores[$oid] = $document;
+ }
+
+ /**
+ * Commits all the scheduled deletions and restorations to the database.
+ */
+ public function commit()
+ {
+ // document deletes
+ if ($this->documentDeletes) {
+ $this->executeDeletes();
+ }
+
+ // document restores
+ if ($this->documentRestores) {
+ $this->executeRestores();
+ }
+ }
+
+ /**
+ * Clears the UnitOfWork and forgets any currently scheduled deletions or restorations.
+ */
+ public function clear()
+ {
+ $this->documentDeletes = array();
+ $this->documentRestores = array();
+ }
+
+ /**
+ * Executes the queued deletions.
+ */
+ private function executeDeletes()
+ {
+ $deletedFieldName = $this->configuration->getDeletedFieldName();
+
+ $documentDeletes = array();
+ foreach ($this->documentDeletes as $document) {
+ $className = get_class($document);
+ $documentDeletes[$className][] = $document;
+ }
+ foreach ($documentDeletes as $className => $documents) {
+ $persister = $this->getDocumentPersister($className);
+ foreach ($documents as $document) {
+
+ if ($this->eventManager->hasListeners(Events::preSoftDelete)) {
+ $this->eventManager->dispatchEvent(Events::preSoftDelete, new Event\LifecycleEventArgs($document, $this->sdm));
+ }
+
+ $persister->addDelete($document);
+ }
+ $persister->executeDeletes();
+
+ $class = $this->dm->getClassMetadata($className);
+
+ $date = new DateTime();
+ foreach ($documents as $document) {
+ $class->setFieldValue($document, $deletedFieldName, $date);
+
+ if ($this->eventManager->hasListeners(Events::postSoftDelete)) {
+ $this->eventManager->dispatchEvent(Events::postSoftDelete, new Event\LifecycleEventArgs($document, $this->sdm));
+ }
+ }
+ }
+ }
+
+ /**
+ * Executes the queued restorations.
+ */
+ private function executeRestores()
+ {
+ $deletedFieldName = $this->configuration->getDeletedFieldName();
+
+ $documentRestores = array();
+ foreach ($this->documentRestores as $document) {
+ $className = get_class($document);
+ $documentRestores[$className][] = $document;
+ }
+ foreach ($documentRestores as $className => $documents) {
+ $persister = $this->getDocumentPersister($className);
+ foreach ($documents as $document) {
+
+ if ($this->eventManager->hasListeners(Events::preSoftDeleteRestore)) {
+ $this->eventManager->dispatchEvent(Events::preSoftDeleteRestore, new Event\LifecycleEventArgs($document, $this->sdm));
+ }
+
+ $persister->addRestore($document);
+ }
+ $persister->executeRestores();
+
+ $class = $this->dm->getClassMetadata($className);
+
+ foreach ($documents as $document) {
+ $class->setFieldValue($document, $deletedFieldName, null);
+
+ if ($this->eventManager->hasListeners(Events::postSoftDeleteRestore)) {
+ $this->eventManager->dispatchEvent(Events::postSoftDeleteRestore, new Event\LifecycleEventArgs($document, $this->sdm));
+ }
+ }
+ }
+ }
+}
1  lib/vendor/doctrine-common
@@ -0,0 +1 @@
+Subproject commit 602de4d6d8813e567c8e5333d85bda28b4583ffc
1  lib/vendor/doctrine-mongodb
@@ -0,0 +1 @@
+Subproject commit a467d22d6ccbcae42aa89f4cdf6d35ea957b5ff9
1  lib/vendor/doctrine-mongodb-odm
@@ -0,0 +1 @@
+Subproject commit a0667bb05867b101333c6031daeedff80f937b8a
18 phpunit.xml.dist
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="false"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="tests/bootstrap.php"
+>
+ <testsuites>
+ <testsuite name="Doctrine ODM MongoDB Soft Delete Test Suite">
+ <directory>./tests/Doctrine/</directory>
+ </testsuite>
+ </testsuites>
+</phpunit>
22 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/ConfigurationTest.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\SoftDelete\Tests;
+
+use PHPUnit_Framework_TestCase;
+use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+
+class ConfigurationTest extends PHPUnit_Framework_TestCase
+{
+ public function testDefaultDeletedFieldName()
+ {
+ $configuration = new Configuration();
+ $this->assertEquals('deletedAt', $configuration->getDeletedFieldName());
+ }
+
+ public function testSetDeletedFieldName()
+ {
+ $configuration = new Configuration();
+ $configuration->setDeletedFieldName('test');
+ $this->assertEquals('test', $configuration->getDeletedFieldName());
+ }
+}
267 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/FunctionalTest.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\SoftDelete\Tests;
+
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\Configuration as ODMConfiguration;
+use Doctrine\ODM\MongoDB\SoftDelete\UnitOfWork;
+use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteManager;
+use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteable;
+use Doctrine\ODM\MongoDB\SoftDelete\Events;
+use Doctrine\ODM\MongoDB\SoftDelete\Event\LifecycleEventArgs;
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver;
+use Doctrine\Common\EventManager;
+use Doctrine\MongoDB\Connection;
+use PHPUnit_Framework_TestCase;
+
+class FunctionalTest extends PHPUnit_Framework_TestCase
+{
+ public function setUp()
+ {
+ $this->sdm = $this->getTestSoftDeleteManager();
+ $this->dm = $this->sdm->getDocumentManager();
+ $this->dm->getDocumentCollection(__NAMESPACE__.'\Seller')->drop();
+ }
+
+ public function testDelete()
+ {
+ $softDeleteable = $this->getTestSoftDeleteable('jwage');
+ $this->dm->persist($softDeleteable);
+ $this->dm->flush();
+
+ $this->sdm->delete($softDeleteable);
+ $this->sdm->flush();
+
+ $this->assertTrue($softDeleteable->isDeleted());
+ $this->assertInstanceOf('DateTime', $softDeleteable->getDeletedAt());
+
+ $check = $this->dm->getDocumentCollection(get_class($softDeleteable))->findOne();
+ $this->assertTrue(isset($check['deletedAt']));
+ $this->assertInstanceOf('MongoDate', $check['deletedAt']);
+ }
+
+ public function testDeleteMultiple()
+ {
+ $softDeleteable1 = $this->getTestSoftDeleteable('jwage1');
+ $softDeleteable2 = $this->getTestSoftDeleteable('jwage2');
+ $this->dm->persist($softDeleteable1);
+ $this->dm->persist($softDeleteable2);
+ $this->dm->flush();
+
+ $this->sdm->delete($softDeleteable1);
+ $this->sdm->delete($softDeleteable2);
+ $this->sdm->flush();
+
+ $this->assertTrue($softDeleteable1->isDeleted());
+ $this->assertInstanceOf('DateTime', $softDeleteable1->getDeletedAt());
+ $this->assertTrue($softDeleteable2->isDeleted());
+ $this->assertInstanceOf('DateTime', $softDeleteable2->getDeletedAt());
+
+ $check1 = $this->dm->getDocumentCollection(get_class($softDeleteable1))->findOne();
+ $this->assertTrue(isset($check1['deletedAt']));
+ $this->assertInstanceOf('MongoDate', $check1['deletedAt']);
+
+ $check2 = $this->dm->getDocumentCollection(get_class($softDeleteable2))->findOne();
+ $this->assertTrue(isset($check2['deletedAt']));
+ $this->assertInstanceOf('MongoDate', $check2['deletedAt']);
+
+ $this->sdm->restore($softDeleteable1);
+ $this->sdm->restore($softDeleteable2);
+ $this->sdm->flush();
+
+ $check1 = $this->dm->getDocumentCollection(get_class($softDeleteable1))->findOne();
+ $this->assertFalse(isset($check1['deletedAt']));
+
+ $check2 = $this->dm->getDocumentCollection(get_class($softDeleteable2))->findOne();
+ $this->assertFalse(isset($check2['deletedAt']));
+ }
+
+ public function testRestore()
+ {
+ $softDeleteable = $this->getTestSoftDeleteable('jwage');
+ $this->dm->persist($softDeleteable);
+ $this->dm->flush();
+
+ $this->sdm->delete($softDeleteable);
+ $this->sdm->flush();
+
+ $check = $this->dm->getDocumentCollection(get_class($softDeleteable))->findOne();
+ $this->assertTrue(isset($check['deletedAt']));
+ $this->assertInstanceOf('MongoDate', $check['deletedAt']);
+
+ $this->sdm->restore($softDeleteable);
+ $this->sdm->flush();
+
+ $this->assertFalse($softDeleteable->isDeleted());
+ $this->assertNull($softDeleteable->getDeletedAt());
+
+ $check = $this->dm->getDocumentCollection(get_class($softDeleteable))->findOne();
+ $this->assertFalse(isset($check['deletedAt']));
+ }
+
+ public function testCreateQueryBuilder()
+ {
+ $softDeleteable = $this->getTestSoftDeleteable('jwage');
+ $this->dm->persist($softDeleteable);
+ $this->dm->flush();
+
+ $check = $this->sdm->createQueryBuilder(get_class($softDeleteable))
+ ->getQuery()
+ ->getSingleResult();
+ $this->assertNotNull($check);
+
+ $this->sdm->delete($softDeleteable);
+ $this->sdm->flush();
+
+ $check = $this->sdm->createQueryBuilder(get_class($softDeleteable))
+ ->getQuery()
+ ->getSingleResult();
+ $this->assertNull($check);
+
+ $check = $this->sdm->createDeletedQueryBuilder(get_class($softDeleteable))
+ ->getQuery()
+ ->getSingleResult();
+ $this->assertNotNull($check);
+ $this->assertTrue($check->isDeleted());
+ $this->assertInstanceOf('DateTime', $check->getDeletedAt());
+ }
+
+ public function testEvents()
+ {
+ $eventManager = $this->sdm->getEventManager();
+ $eventSubscriber = new TestEventSubscriber();
+ $eventManager->addEventSubscriber($eventSubscriber);
+
+ $softDeleteable = $this->getTestSoftDeleteable('jwage');
+ $this->dm->persist($softDeleteable);
+ $this->dm->flush();
+
+ $this->sdm->delete($softDeleteable);
+ $this->sdm->flush();
+
+ $this->assertEquals(array('preSoftDelete', 'postSoftDelete'), $eventSubscriber->called);
+
+ $eventSubscriber->called = array();
+
+ $this->sdm->restore($softDeleteable);
+ $this->sdm->flush();
+
+ $this->assertEquals(array('preSoftDeleteRestore', 'postSoftDeleteRestore'), $eventSubscriber->called);
+ }
+
+ private function getTestDocumentManager()
+ {
+ $configuration = new ODMConfiguration();
+ $configuration->setHydratorDir(__DIR__);
+ $configuration->setHydratorNamespace('TestHydrator');
+ $configuration->setProxyDir(__DIR__);
+ $configuration->setProxyNamespace('TestProxy');
+
+ $reader = new AnnotationReader();
+ $reader->setDefaultAnnotationNamespace('Doctrine\ODM\MongoDB\Mapping\\');
+ $annotationDriver = new AnnotationDriver($reader, __DIR__ . '/Documents');
+ $configuration->setMetadataDriverImpl($annotationDriver);
+
+ $conn = new Connection(null, array(), $configuration);
+ return DocumentManager::create($conn, $configuration);
+ }
+
+ public function getTestSoftDeleteable($name)
+ {
+ return new Seller($name);
+ }
+
+ public function getTestConfiguration()
+ {
+ return new Configuration();
+ }
+
+ public function getTestUnitOfWork(DocumentManager $dm, Configuration $configuration)
+ {
+ return new UnitOfWork($dm, $configuration);
+ }
+
+ public function getTestEventManager()
+ {
+ return new EventManager();
+ }
+
+ private function getTestSoftDeleteManager()
+ {
+ $dm = $this->getTestDocumentManager();
+ $configuration = $this->getTestConfiguration();
+ $unitOfWork = $this->getTestUnitOfWork($dm, $configuration);
+ $eventManager = $this->getTestEventManager();
+ return new SoftDeleteManager($dm, $configuration, $unitOfWork, $eventManager);
+ }
+}
+
+class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
+{
+ public $called = array();
+
+ public function preSoftDelete(LifecycleEventArgs $args)
+ {
+ $this->called[] = 'preSoftDelete';
+ }
+
+ public function postSoftDelete(LifecycleEventArgs $args)
+ {
+ $this->called[] = 'postSoftDelete';
+ }
+
+ public function preSoftDeleteRestore(LifecycleEventArgs $args)
+ {
+ $this->called[] = 'preSoftDeleteRestore';
+ }
+
+ public function postSoftDeleteRestore(LifecycleEventArgs $args)
+ {
+ $this->called[] = 'postSoftDeleteRestore';
+ }
+
+ public function getSubscribedEvents()
+ {
+ return array(
+ Events::preSoftDelete,
+ Events::postSoftDelete,
+ Events::preSoftDeleteRestore,
+ Events::postSoftDeleteRestore
+ );
+ }
+}
+
+/** @Document */
+class Seller implements SoftDeleteable
+{
+ /** @Id */
+ private $id;
+
+ /** @Date @Index */
+ private $deletedAt;
+
+ /** @String */
+ private $name;
+
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ public function getDeletedAt()
+ {
+ return $this->deletedAt;
+ }
+
+ public function isDeleted()
+ {
+ return $this->deletedAt !== null ? true : false;
+ }
+}
151 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/PersisterTest.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\SoftDelete\Tests;
+
+use PHPUnit_Framework_TestCase;
+use Doctrine\ODM\MongoDB\SoftDelete\Persister;
+use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
+use Doctrine\MongoDB\Collection;
+use MongoDate;
+
+class PersisterTest extends PHPUnit_Framework_TestCase
+{
+ public function testConstructor()
+ {
+ $class = $this->getMockClassMetadata();
+ $collection = $this->getMockCollection();
+ $configuration = $this->getMockConfiguration();
+ $persister = $this->getTestPersister($configuration, $class, $collection);
+ $this->assertSame($class, $persister->getClass());
+ $this->assertSame($collection, $persister->getCollection());
+ }
+
+ public function testAddDelete()
+ {
+ $class = $this->getMockClassMetadata();
+ $collection = $this->getMockCollection();
+ $configuration = $this->getMockConfiguration();
+ $persister = $this->getTestPersister($configuration, $class, $collection);
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $persister->addDelete($mockSoftDeleteable);
+
+ $expects = array(spl_object_hash($mockSoftDeleteable) => $mockSoftDeleteable);
+ $this->assertEquals($expects, $persister->getDeletes());
+ }
+
+ public function testAddRestore()
+ {
+ $class = $this->getMockClassMetadata();
+ $collection = $this->getMockCollection();
+ $configuration = $this->getMockConfiguration();
+ $persister = $this->getTestPersister($configuration, $class, $collection); $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $persister->addRestore($mockSoftDeleteable);
+
+ $expects = array(spl_object_hash($mockSoftDeleteable) => $mockSoftDeleteable);
+ $this->assertEquals($expects, $persister->getRestores());
+ }
+
+ public function testExecuteDeletes()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+
+ $class = $this->getMockClassMetadata();
+ $class->expects($this->once())
+ ->method('getIdentifierObject')
+ ->with($mockSoftDeleteable)
+ ->will($this->returnValue(1));
+
+ $date = new MongoDate();
+
+ $collection = $this->getMockCollection();
+ $collection->expects($this->once())
+ ->method('update')
+ ->with(
+ array('_id' => array('$in' => array(1))),
+ array('$set' => array(
+ 'deletedAt' => $date
+ )),
+ array(
+ 'multiple' => true,
+ 'safe' => true
+ )
+ );
+
+ $configuration = $this->getMockConfiguration();
+ $configuration->expects($this->once())
+ ->method('getDeletedFieldName')
+ ->will($this->returnValue('deletedAt'));
+
+ $persister = $this->getTestPersister($configuration, $class, $collection);
+ $persister->addDelete($mockSoftDeleteable);
+ $persister->executeDeletes($date);
+ }
+
+ public function testExecuteRestores()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+
+ $class = $this->getMockClassMetadata();
+ $class->expects($this->once())
+ ->method('getIdentifierObject')
+ ->with($mockSoftDeleteable)
+ ->will($this->returnValue(1));
+
+ $collection = $this->getMockCollection();
+ $collection->expects($this->once())
+ ->method('update')
+ ->with(
+ array('_id' => array('$in' => array(1))),
+ array('$unset' => array(
+ 'deletedAt' => true
+ )),
+ array(
+ 'multiple' => true,
+ 'safe' => true
+ )
+ );
+
+ $configuration = $this->getMockConfiguration();
+ $configuration->expects($this->once())
+ ->method('getDeletedFieldName')
+ ->will($this->returnValue('deletedAt'));
+
+ $persister = $this->getTestPersister($configuration, $class, $collection);
+ $persister->addRestore($mockSoftDeleteable);
+ $persister->executeRestores();
+ }
+
+ private function getMockSoftDeletable()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteable')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockClassMetadata()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\Mapping\ClassMetadata')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockCollection()
+ {
+ return $this->getMockBuilder('Doctrine\MongoDB\Collection')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockConfiguration()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\SoftDelete\Configuration')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getTestPersister(Configuration $configuration, ClassMetadata $class, Collection $collection)
+ {
+ return new Persister($configuration, $class, $collection);
+ }
+}
34 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/SoftDeletableTest.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\SoftDelete\Tests;
+
+use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+use PHPUnit_Framework_TestCase;
+use DateTime;
+
+class SoftDeletableTest extends PHPUnit_Framework_TestCase
+{
+ public function testSoftDeleteable()
+ {
+ $date = new DateTime();
+
+ $mockSoftDeleteable = $this->getMockSoftDeleteable();
+ $mockSoftDeleteable->expects($this->once())
+ ->method('getDeletedAt')
+ ->will($this->returnValue($date));
+
+ $mockSoftDeleteable->expects($this->once())
+ ->method('isDeleted')
+ ->will($this->returnValue(true));
+
+ $this->assertSame($date, $mockSoftDeleteable->getDeletedAt());
+ $this->assertTrue($mockSoftDeleteable->isDeleted());
+ }
+
+ private function getMockSoftDeleteable()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteable')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+}
179 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/SoftDeleteManagerTest.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\SoftDelete\Tests;
+
+use PHPUnit_Framework_TestCase;
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteManager;
+use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+use Doctrine\ODM\MongoDB\SoftDelete\UnitOfWork;
+
+class SoftDeleteManagerTest extends PHPUnit_Framework_TestCase
+{
+ public function testConstructor()
+ {
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getConfiguration();
+ $uow = $this->getMockUnitOfWork();
+ $sdm = $this->getTestSoftDeleteManager($dm, $configuration, $uow);
+ $this->assertSame($dm, $sdm->getDocumentManager());
+ }
+
+ public function testCreateQueryBuilder()
+ {
+ $mockQb = $this->getMockQb();
+
+ $dm = $this->getMockDocumentManager();
+ $dm->expects($this->once())
+ ->method('createQueryBuilder')
+ ->will($this->returnValue($mockQb));
+
+ $mockQb->expects($this->once())
+ ->method('field')
+ ->with('deletedAt')
+ ->will($this->returnValue($mockQb));
+
+ $mockQb->expects($this->once())
+ ->method('exists')
+ ->with(false)
+ ->will($this->returnValue($mockQb));
+
+ $configuration = $this->getConfiguration();
+ $uow = $this->getMockUnitOfWork();
+ $sdm = $this->getTestSoftDeleteManager($dm, $configuration, $uow);
+
+ $qb = $sdm->createQueryBuilder();
+ $this->assertSame($mockQb, $qb);
+ }
+
+ public function testCreateDeletedQueryBuilder()
+ {
+ $mockQb = $this->getMockQb();
+
+ $dm = $this->getMockDocumentManager();
+ $dm->expects($this->once())
+ ->method('createQueryBuilder')
+ ->will($this->returnValue($mockQb));
+
+ $mockQb->expects($this->once())
+ ->method('field')
+ ->with('deletedAt')
+ ->will($this->returnValue($mockQb));
+
+ $mockQb->expects($this->once())
+ ->method('exists')
+ ->with(true)
+ ->will($this->returnValue($mockQb));
+
+ $configuration = $this->getConfiguration();
+ $uow = $this->getMockUnitOfWork();
+ $sdm = $this->getTestSoftDeleteManager($dm, $configuration, $uow);
+
+ $qb = $sdm->createDeletedQueryBuilder();
+ $this->assertSame($mockQb, $qb);
+ }
+
+ public function testDelete()
+ {
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getConfiguration();
+ $uow = $this->getMockUnitOfWork();
+
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+
+ $uow->expects($this->once())
+ ->method('delete')
+ ->with($mockSoftDeleteable);
+
+ $sdm = $this->getTestSoftDeleteManager($dm, $configuration, $uow);
+
+ $sdm->delete($mockSoftDeleteable);
+ }
+
+ public function testRestore()
+ {
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getConfiguration();
+ $uow = $this->getMockUnitOfWork();
+
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+
+ $uow->expects($this->once())
+ ->method('restore')
+ ->with($mockSoftDeleteable);
+
+ $sdm = $this->getTestSoftDeleteManager($dm, $configuration, $uow);
+
+ $sdm->restore($mockSoftDeleteable);
+ }
+
+ public function testFlush()
+ {
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getConfiguration();
+ $uow = $this->getMockUnitOfWork();
+
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+
+ $uow->expects($this->once())
+ ->method('commit');
+
+ $sdm = $this->getTestSoftDeleteManager($dm, $configuration, $uow);
+
+ $sdm->flush();
+ }
+
+ public function testClear()
+ {
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getConfiguration();
+ $uow = $this->getMockUnitOfWork();
+
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+
+ $uow->expects($this->once())
+ ->method('clear');
+
+ $sdm = $this->getTestSoftDeleteManager($dm, $configuration, $uow);
+
+ $sdm->clear();
+ }
+
+ private function getTestSoftDeleteManager(DocumentManager $dm, Configuration $configuration, UnitOfWork $uow)
+ {
+ return new SoftDeleteManager($dm, $configuration, $uow);
+ }
+
+ private function getMockSoftDeletable()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteable')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockDocumentManager()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\DocumentManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockQb()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\Query\Builder')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getConfiguration()
+ {
+ return new Configuration();
+ }
+
+ private function getMockUnitOfWork()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\SoftDelete\UnitOfWork')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+}
139 tests/Doctrine/ODM/MongoDB/SoftDelete/Tests/UnitOfWorkTest.php
@@ -0,0 +1,139 @@
+<?php
+
+namespace Doctrine\ODM\MongoDB\SoftDelete\Tests;
+
+use Doctrine\ODM\MongoDB\DocumentManager;
+use Doctrine\ODM\MongoDB\SoftDelete\UnitOfWork;
+use Doctrine\ODM\MongoDB\SoftDelete\Configuration;
+use PHPUnit_Framework_TestCase;
+
+class UnitOfWorkTest extends PHPUnit_Framework_TestCase
+{
+ public function testConstructor()
+ {
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getMockConfiguration();
+ $uow = $this->getTestUnitOfWork($dm, $configuration);
+ $this->assertSame($dm, $uow->getDocumentManager());
+ }
+
+ public function testDelete()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $mockSoftDeleteable->expects($this->once())
+ ->method('isDeleted')
+ ->will($this->returnValue(false));
+
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getMockConfiguration();
+ $uow = $this->getTestUnitOfWork($dm, $configuration);
+ $uow->delete($mockSoftDeleteable);
+ $this->assertTrue($uow->isScheduledForDelete($mockSoftDeleteable));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testDeleteThrowsExceptionIfAlreadyDeleted()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $mockSoftDeleteable->expects($this->once())
+ ->method('isDeleted')
+ ->will($this->returnValue(true));
+
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getMockConfiguration();
+ $uow = $this->getTestUnitOfWork($dm, $configuration);
+ $uow->delete($mockSoftDeleteable);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testDeleteThrowsExceptionIfAlreadyScheduledForDelete()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $mockSoftDeleteable->expects($this->any())
+ ->method('isDeleted')
+ ->will($this->returnValue(false));
+
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getMockConfiguration();
+ $uow = $this->getTestUnitOfWork($dm, $configuration);
+ $uow->delete($mockSoftDeleteable);
+ $uow->delete($mockSoftDeleteable);
+ }
+
+ public function testRestore()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $mockSoftDeleteable->expects($this->once())
+ ->method('isDeleted')
+ ->will($this->returnValue(true));
+
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getMockConfiguration();
+ $uow = $this->getTestUnitOfWork($dm, $configuration);
+ $uow->restore($mockSoftDeleteable);
+ $this->assertTrue($uow->isScheduledForRestore($mockSoftDeleteable));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testRestoreThrowsExceptionIfNotDeleted()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $mockSoftDeleteable->expects($this->once())
+ ->method('isDeleted')
+ ->will($this->returnValue(false));
+
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getMockConfiguration();
+ $uow = $this->getTestUnitOfWork($dm, $configuration);
+ $uow->restore($mockSoftDeleteable);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testRestoreThrowsExceptionIfAlreadyScheduledForRestore()
+ {
+ $mockSoftDeleteable = $this->getMockSoftDeletable();
+ $mockSoftDeleteable->expects($this->any())
+ ->method('isDeleted')
+ ->will($this->returnValue(false));
+
+ $dm = $this->getMockDocumentManager();
+ $configuration = $this->getMockConfiguration();
+ $uow = $this->getTestUnitOfWork($dm, $configuration);
+ $uow->restore($mockSoftDeleteable);
+ $uow->restore($mockSoftDeleteable);
+ }
+
+ private function getTestUnitOfWork(DocumentManager $dm, Configuration $configuration)
+ {
+ return new UnitOfWork($dm, $configuration);
+ }
+
+ private function getMockConfiguration()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\SoftDelete\Configuration')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockDocumentManager()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\DocumentManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ private function getMockSoftDeletable()
+ {
+ return $this->getMockBuilder('Doctrine\ODM\MongoDB\SoftDelete\SoftDeleteable')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+}
20 tests/bootstrap.php
@@ -0,0 +1,20 @@
+<?php
+
+require_once __DIR__ . '/../lib/vendor/doctrine-common/lib/Doctrine/Common/ClassLoader.php';
+
+use Doctrine\Common\ClassLoader;
+
+$classLoader = new ClassLoader('Doctrine\ODM\MongoDB\SoftDelete\Tests', __DIR__);
+$classLoader->register();
+
+$classLoader = new ClassLoader('Doctrine\ODM\MongoDB\SoftDelete', __DIR__ . '/../lib');
+$classLoader->register();
+
+$classLoader = new ClassLoader('Doctrine\ODM\MongoDB', __DIR__ . '/../lib/vendor/doctrine-mongodb-odm/lib');
+$classLoader->register();
+
+$classLoader = new ClassLoader('Doctrine\MongoDB', __DIR__ . '/../lib/vendor/doctrine-mongodb/lib');
+$classLoader->register();
+
+$classLoader = new ClassLoader('Doctrine\Common', __DIR__ . '/../lib/vendor/doctrine-common/lib');
+$classLoader->register();
Please sign in to comment.
Something went wrong with that request. Please try again.