Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of git://github.com/doctrine/data-fixtures into…

… nosubmodules
  • Loading branch information...
commit a6844ead89cdfe1862c6172be6486e33107611b0 2 parents 2359be4 + d6b4470
@ornicar authored
View
37 lib/Doctrine/Common/DataFixtures/DependentFixtureInterface.php
@@ -0,0 +1,37 @@
+<?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\Common\DataFixtures;
+
+/**
+ * DependentFixtureInterface needs to be implemented
+ * by fixtures which depend on other fixtures
+ *
+ * @author Gustavo Adrian <comfortablynumb@gmail.com>
+ */
+interface DependentFixtureInterface
+{
+ /*
+ * This method must return an array of fixtures classes
+ * on which the implementing class depends on
+ *
+ * @return array
+ */
+ function getDependencies();
+}
View
75 lib/Doctrine/Common/DataFixtures/Event/Listener/MongoDBReferenceListener.php
@@ -0,0 +1,75 @@
+<?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\Common\DataFixtures\Event\Listener;
+
+use Doctrine\Common\EventSubscriber;
+use Doctrine\Common\DataFixtures\ReferenceRepository;
+use Doctrine\ODM\MongoDB\Event\LifecycleEventArgs;
+
+/**
+ * Reference Listener populates identities for
+ * stored references
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ */
+final class MongoDBReferenceListener implements EventSubscriber
+{
+ /**
+ * @var ReferenceRepository
+ */
+ private $referenceRepository;
+
+ /**
+ * Initialize listener
+ *
+ * @param ReferenceRepository $referenceRepository
+ */
+ public function __construct(ReferenceRepository $referenceRepository)
+ {
+ $this->referenceRepository = $referenceRepository;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubscribedEvents()
+ {
+ return array(
+ 'postPersist'
+ );
+ }
+
+ /**
+ * Populates identities for stored references
+ *
+ * @param LifecycleEventArgs $args
+ */
+ public function postPersist(LifecycleEventArgs $args)
+ {
+ $object = $args->getDocument();
+ if (($name = $this->referenceRepository->getReferenceName($object)) !== false) {
+ $identity = $args->getDocumentManager()
+ ->getUnitOfWork()
+ ->getDocumentIdentifier($object);
+ $this->referenceRepository->setReferenceIdentity($name, $identity);
+ }
+ }
+}
View
75 lib/Doctrine/Common/DataFixtures/Event/Listener/ORMReferenceListener.php
@@ -0,0 +1,75 @@
+<?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\Common\DataFixtures\Event\Listener;
+
+use Doctrine\Common\EventSubscriber;
+use Doctrine\Common\DataFixtures\ReferenceRepository;
+use Doctrine\ORM\Event\LifecycleEventArgs;
+
+/**
+ * Reference Listener populates identities for
+ * stored references
+ *
+ * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
+ */
+final class ORMReferenceListener implements EventSubscriber
+{
+ /**
+ * @var ReferenceRepository
+ */
+ private $referenceRepository;
+
+ /**
+ * Initialize listener
+ *
+ * @param ReferenceRepository $referenceRepository
+ */
+ public function __construct(ReferenceRepository $referenceRepository)
+ {
+ $this->referenceRepository = $referenceRepository;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSubscribedEvents()
+ {
+ return array(
+ 'postPersist' // would be better to use onClear, but it is supported only in 2.1
+ );
+ }
+
+ /**
+ * Populates identities for stored references
+ *
+ * @param LifecycleEventArgs $args
+ */
+ public function postPersist(LifecycleEventArgs $args)
+ {
+ $object = $args->getEntity();
+ if (($name = $this->referenceRepository->getReferenceName($object)) !== false) {
+ $identity = $args->getEntityManager()
+ ->getUnitOfWork()
+ ->getEntityIdentifier($object);
+ $this->referenceRepository->setReferenceIdentity($name, $identity);
+ }
+ }
+}
View
9 lib/Doctrine/Common/DataFixtures/Exception/CircularReferenceException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Doctrine\Common\DataFixtures\Exception;
+
+use Doctrine\Common\CommonException;
+
+class CircularReferenceException extends CommonException
+{
+}
View
4 lib/Doctrine/Common/DataFixtures/Executor/MongoDBExecutor.php
@@ -21,6 +21,7 @@
use Doctrine\ODM\MongoDB\DocumentManager;
use Doctrine\Common\DataFixtures\Purger\MongoDBPurger;
+use Doctrine\Common\DataFixtures\Event\Listener\MongoDBReferenceListener;
/**
* Class responsible for executing data fixtures.
@@ -42,6 +43,9 @@ public function __construct(DocumentManager $dm, MongoDBPurger $purger = null)
$this->purger->setDocumentManager($dm);
}
parent::__construct($dm);
+ $dm->getEventManager()->addEventSubscriber(
+ new MongoDBReferenceListener($this->referenceRepository)
+ );
}
/** @inheritDoc */
View
4 lib/Doctrine/Common/DataFixtures/Executor/ORMExecutor.php
@@ -21,6 +21,7 @@
use Doctrine\ORM\EntityManager;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
+use Doctrine\Common\DataFixtures\Event\Listener\ORMReferenceListener;
/**
* Class responsible for executing data fixtures.
@@ -42,6 +43,9 @@ public function __construct(EntityManager $em, ORMPurger $purger = null)
$this->purger->setEntityManager($em);
}
parent::__construct($em);
+ $em->getEventManager()->addEventSubscriber(
+ new ORMReferenceListener($this->referenceRepository)
+ );
}
/** @inheritDoc */
View
185 lib/Doctrine/Common/DataFixtures/Loader.php
@@ -20,6 +20,7 @@
namespace Doctrine\Common\DataFixtures;
use Doctrine\ORM\EntityManager;
+use Doctrine\Common\DataFixtures\Exception\CircularReferenceException;
/**
* Class responsible for loading data fixture classes.
@@ -43,6 +44,20 @@ class Loader
private $orderedFixtures;
/**
+ * Determines if we must order fixtures by number
+ *
+ * @var boolean
+ */
+ private $orderFixturesByNumber = false;
+
+ /**
+ * Determines if we must order fixtures by its dependencies
+ *
+ * @var boolean
+ */
+ private $orderFixturesByDependencies = false;
+
+ /**
* The file extension of fixture files.
*
* @var string
@@ -78,10 +93,11 @@ public function loadFromDirectory($dir)
$includedFiles[] = $sourceFile;
}
$declared = get_declared_classes();
- $fixtures = array();
+
foreach ($declared as $className) {
$reflClass = new \ReflectionClass($className);
$sourceFile = $reflClass->getFileName();
+
if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
$fixture = new $className;
$fixtures[] = $fixture;
@@ -98,8 +114,22 @@ public function loadFromDirectory($dir)
*/
public function addFixture(FixtureInterface $fixture)
{
- $this->orderedFixtures = null;
- $this->fixtures[] = $fixture;
+ $fixtureClass = get_class($fixture);
+
+ if (!isset($this->fixtures[$fixtureClass])) {
+ if ($fixture instanceof OrderedFixtureInterface && $fixture instanceof DependentFixtureInterface) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" can\'t implement "%s" and "%s" at the same time.',
+ get_class($fixture),
+ 'OrderedFixtureInterface',
+ 'DependentFixtureInterface'));
+ } elseif ($fixture instanceof OrderedFixtureInterface) {
+ $this->orderFixturesByNumber = true;
+ } elseif ($fixture instanceof DependentFixtureInterface) {
+ $this->orderFixturesByDependencies = true;
+ }
+
+ $this->fixtures[$fixtureClass] = $fixture;
+ }
}
/**
@@ -109,9 +139,18 @@ public function addFixture(FixtureInterface $fixture)
*/
public function getFixtures()
{
- if ($this->orderedFixtures === null) {
- $this->orderFixtures();
+ if ($this->orderFixturesByNumber) {
+ $this->orderFixturesByNumber();
}
+
+ if ($this->orderFixturesByDependencies) {
+ $this->orderFixturesByDependencies();
+ }
+
+ if (!$this->orderFixturesByNumber && !$this->orderFixturesByDependencies) {
+ $this->orderedFixtures = $this->fixtures;
+ }
+
return $this->orderedFixtures;
}
@@ -123,17 +162,20 @@ public function getFixtures()
*/
public function isTransient($className)
{
+ $rc = new \ReflectionClass($className);
+ if ($rc->isAbstract()) return true;
+
$interfaces = class_implements($className);
return in_array('Doctrine\Common\DataFixtures\FixtureInterface', $interfaces) ? false : true;
}
-
+
/**
- * Orders fixtures
+ * Orders fixtures by number
*
* @todo maybe there is a better way to handle reordering
* @return void
*/
- private function orderFixtures()
+ private function orderFixturesByNumber()
{
$this->orderedFixtures = $this->fixtures;
usort($this->orderedFixtures, function($a, $b) {
@@ -150,4 +192,129 @@ private function orderFixtures()
return 0;
});
}
-}
+
+
+ /**
+ * Orders fixtures by dependencies
+ *
+ * @return void
+ */
+ private function orderFixturesByDependencies()
+ {
+ $sequenceForClasses = array();
+
+ // If fixtures were already ordered by number then we need
+ // to remove classes which are not instances of OrderedFixtureInterface
+ // in case fixtures implementing DependentFixtureInterface exist.
+ // This is because, in that case, the method orderFixturesByDependencies
+ // will handle all fixtures which are not instances of
+ // OrderedFixtureInterface
+ if ($this->orderFixturesByNumber) {
+ $count = count($this->orderedFixtures);
+
+ for ($i = 0 ; $i < $count ; ++$i) {
+ if (!($this->orderedFixtures[$i] instanceof OrderedFixtureInterface)) {
+ unset($this->orderedFixtures[$i]);
+ }
+ }
+ }
+
+ // First we determine which classes has dependencies and which don't
+ foreach ($this->fixtures as $fixture) {
+ $fixtureClass = get_class($fixture);
+
+ if ($fixture instanceof OrderedFixtureInterface) {
+ continue;
+ } elseif ($fixture instanceof DependentFixtureInterface) {
+ $dependenciesClasses = $fixture->getDependencies();
+
+ $this->validateDependencies($dependenciesClasses);
+
+ if (!is_array($dependenciesClasses) || empty($dependenciesClasses)) {
+ throw new \InvalidArgumentException(sprintf('Method "%s" in class "%s" must return an array of classes which are dependencies for the fixture, and it must be NOT empty.', 'getDependencies', $fixtureClass));
+ }
+
+ if (in_array($fixtureClass, $dependenciesClasses)) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" can\'t have itself as a dependency', $fixtureClass));
+ }
+
+ // We mark this class as unsequenced
+ $sequenceForClasses[$fixtureClass] = -1;
+ } else {
+ // This class has no dependencies, so we assign 0
+ $sequenceForClasses[$fixtureClass] = 0;
+ }
+ }
+
+ // Now we order fixtures by sequence
+ $sequence = 1;
+ $lastCount = -1;
+
+ while (($count = count($unsequencedClasses = $this->getUnsequencedClasses($sequenceForClasses))) > 0 && $count !== $lastCount) {
+ foreach ($unsequencedClasses as $key => $class) {
+ $fixture = $this->fixtures[$class];
+ $dependencies = $fixture->getDependencies();
+ $unsequencedDependencies = $this->getUnsequencedClasses($sequenceForClasses, $dependencies);
+
+ if (count($unsequencedDependencies) === 0) {
+ $sequenceForClasses[$class] = $sequence++;
+ }
+ }
+
+ $lastCount = $count;
+ }
+
+ $orderedFixtures = array();
+
+ // If there're fixtures unsequenced left and they couldn't be sequenced,
+ // it means we have a circular reference
+ if ($count > 0) {
+ $msg = 'Classes "%s" have produced a CircularReferenceException. ';
+ $msg .= 'An example of this problem would be the following: Class C has class B as its dependency. ';
+ $msg .= 'Then, class B has class A has its dependency. Finally, class A has class C as its dependency. ';
+ $msg .= 'This case would produce a CircularReferenceException.';
+
+ throw new CircularReferenceException(sprintf($msg, implode(',', $unsequencedClasses)));
+ } else {
+ // We order the classes by sequence
+ asort($sequenceForClasses);
+
+ foreach ($sequenceForClasses as $class => $sequence) {
+ // If fixtures were ordered
+ $orderedFixtures[] = $this->fixtures[$class];
+ }
+ }
+
+ $this->orderedFixtures = is_array($this->orderedFixtures) ? array_merge($this->orderedFixtures, $orderedFixtures) : $orderedFixtures;
+ }
+
+ private function validateDependencies($dependenciesClasses)
+ {
+ $loadedFixtureClasses = array_keys($this->fixtures);
+
+ foreach ($dependenciesClasses as $class) {
+ if (!in_array($class, $loadedFixtureClasses)) {
+ throw new \RuntimeException(sprintf('Fixture "%s" was declared as a dependency, but it doesn\'t exist.', $class));
+ }
+ }
+
+ return true;
+ }
+
+ private function getUnsequencedClasses($sequences, $classes = null)
+ {
+ $unsequencedClasses = array();
+
+ if (is_null($classes)) {
+ $classes = array_keys($sequences);
+ }
+
+ foreach ($classes as $class) {
+ if ($sequences[$class] === -1) {
+ $unsequencedClasses[] = $class;
+ }
+ }
+
+ return $unsequencedClasses;
+ }
+}
View
100 lib/Doctrine/Common/DataFixtures/ReferenceRepository.php
@@ -23,7 +23,7 @@
* ReferenceRepository class manages references for
* fixtures in order to easily support the relations
* between fixtures
- *
+ *
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
*/
class ReferenceRepository
@@ -31,57 +31,79 @@ class ReferenceRepository
/**
* List of named references to the fixture objects
* gathered during loads of fixtures
- *
+ *
* @var array
*/
private $references = array();
-
+
+ /**
+ * List of identifiers stored for references
+ * in case if reference gets unmanaged, it will
+ * use a proxy referenced by this identity
+ *
+ * @var array
+ */
+ private $identities = array();
+
/**
* Currently used object manager
- *
+ *
* @var object - object manager
*/
private $manager;
-
+
/**
* Initialize the ReferenceRepository
- *
+ *
* @param object $manager
*/
public function __construct($manager)
{
$this->manager = $manager;
}
-
+
/**
* Set the reference entry identified by $name
- * and referenced to managed $object. If $name
+ * and referenced to $reference. If $name
* already is set, it overrides it
*
* @param string $name
- * @param object $object - managed object
- * @throws LogicException - if object is not mapped or
- * does not have identifier yet
- * @return void
+ * @param object $reference
*/
- public function setReference($name, $object)
+ public function setReference($name, $reference)
{
- $this->references[$name] = $object;
+ $this->references[$name] = $reference;
+ // in case if reference is set after flush, store its identity
+ $uow = $this->manager->getUnitOfWork();
+ if ($uow->isInIdentityMap($reference)) {
+ $this->identities[$name] = $uow->getEntityIdentifier($reference);
+ }
}
-
+
+ /**
+ * Store the identifier of a reference
+ *
+ * @param string $name
+ * @param mixed $identity
+ */
+ public function setReferenceIdentity($name, $identity)
+ {
+ $this->identities[$name] = $identity;
+ }
+
/**
* Set the reference entry identified by $name
* and referenced to managed $object. $name must
* not be set yet
- *
+ *
* Notice: in case if identifier is generated after
* the record is inserted, be sure tu use this method
* after $object is flushed
- *
+ *
* @param string $name
* @param object $object - managed object
* @throws BadMethodCallException - if repository already has
- * a reference by $name
+ * a reference by $name
* @return void
*/
public function addReference($name, $object)
@@ -91,22 +113,54 @@ public function addReference($name, $object)
}
$this->setReference($name, $object);
}
-
+
/**
* Loads an object using stored reference
* named by $name
- *
+ *
* @param string $name
* @return object
*/
public function getReference($name)
{
- return $this->references[$name];
+ $reference = $this->references[$name];
+ $meta = $this->manager->getClassMetadata(get_class($reference));
+ $uow = $this->manager->getUnitOfWork();
+ if (!$uow->isInIdentityMap($reference) && isset($this->identities[$name])) {
+ $reference = $this->manager->getReference(
+ $meta->name,
+ $this->identities[$name]
+ );
+ $this->references[$name] = $reference; // allready in identity map
+ }
+ return $reference;
}
-
+
+ /**
+ * Searches for a reference name in the
+ * list of stored references
+ *
+ * @param object $reference
+ * @return string
+ */
+ public function getReferenceName($reference)
+ {
+ return array_search($reference, $this->references, true);
+ }
+
+ /**
+ * Checks if reference has identity stored
+ *
+ * @param string $name
+ */
+ public function hasIdentity($name)
+ {
+ return array_key_exists($name, $this->identities);
+ }
+
/**
* Get all stored references
- *
+ *
* @return array
*/
public function getReferences()
View
17 tests/Doctrine/Tests/Common/DataFixtures/BaseTest.php
@@ -59,11 +59,11 @@ protected function getMockEntityManager()
$em = EntityManager::create($conn, $config);
return $em;
}
-
+
/**
* EntityManager mock object together with
* annotation mapping driver
- *
+ *
* @return EntityManager
*/
protected function getMockAnnotationReaderEntityManager()
@@ -93,7 +93,7 @@ protected function getMockAnnotationReaderEntityManager()
$reader,
__DIR__ . '/TestEntity'
);
-
+
$config->expects($this->any())
->method('getMetadataDriverImpl')
->will($this->returnValue($mappingDriver));
@@ -101,12 +101,12 @@ protected function getMockAnnotationReaderEntityManager()
$em = EntityManager::create($conn, $config);
return $em;
}
-
+
/**
* EntityManager mock object together with
* annotation mapping driver and pdo_sqlite
* database in memory
- *
+ *
* @return EntityManager
*/
protected function getMockSqliteEntityManager()
@@ -124,7 +124,7 @@ protected function getMockSqliteEntityManager()
$config->expects($this->once())
->method('getProxyNamespace')
->will($this->returnValue('Proxy'));
-
+
$config->expects($this->once())
->method('getAutoGenerateProxyClasses')
->will($this->returnValue(true));
@@ -132,13 +132,12 @@ protected function getMockSqliteEntityManager()
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setDefaultAnnotationNamespace('Doctrine\ORM\Mapping\\');
$mappingDriver = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($reader);
-
+
$config->expects($this->any())
->method('getMetadataDriverImpl')
->will($this->returnValue($mappingDriver));
- $evm = $this->getMock('Doctrine\Common\EventManager');
- $em = EntityManager::create($conn, $config, $evm);
+ $em = EntityManager::create($conn, $config);
return $em;
}
}
View
384 tests/Doctrine/Tests/Common/DataFixtures/DependentFixtureTest.php
@@ -0,0 +1,384 @@
+<?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\Tests\Common\DataFixtures;
+
+use Doctrine\Common\DataFixtures\Loader;
+use Doctrine\Common\DataFixtures\DependentFixtureInterface;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Common\DataFixtures\FixtureInterface;
+
+require_once __DIR__.'/TestInit.php';
+
+/**
+ * Test Fixture ordering by dependencies.
+ *
+ * @author Gustavo Adrian <comfortablynumb84@gmail.com>
+ */
+class DependentFixtureTest extends BaseTest
+{
+ public function test_orderFixturesByDependencies_orderClassesWithASingleParent()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new DependentFixture3);
+ $loader->addFixture(new DependentFixture1);
+ $loader->addFixture(new DependentFixture2);
+ $loader->addFixture(new BaseParentFixture1);
+
+ $orderedFixtures = $loader->getFixtures();
+
+ $this->assertEquals(4, count($orderedFixtures));
+
+ $this->assertTrue(array_shift($orderedFixtures) instanceof BaseParentFixture1);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof DependentFixture1);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof DependentFixture2);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof DependentFixture3);
+ }
+
+ public function test_orderFixturesByDependencies_orderClassesWithAMultipleParents()
+ {
+ $loader = new Loader();
+
+ $addressFixture = new AddressFixture();
+ $contactMethodFixture = new ContactMethodFixture();
+ $contactFixture = new ContactFixture();
+ $baseParentFixture = new BaseParentFixture1();
+ $countryFixture = new CountryFixture();
+ $stateFixture = new StateFixture();
+
+ $loader->addFixture($addressFixture);
+ $loader->addFixture($contactMethodFixture);
+ $loader->addFixture($contactFixture);
+ $loader->addFixture($baseParentFixture);
+ $loader->addFixture($countryFixture);
+ $loader->addFixture($stateFixture);
+
+ $orderedFixtures = $loader->getFixtures();
+
+ $this->assertEquals(6, count($orderedFixtures));
+
+ $contactFixtureOrder = array_search($contactFixture, $orderedFixtures);
+ $contactMethodFixtureOrder = array_search($contactMethodFixture, $orderedFixtures);
+ $addressFixtureOrder = array_search($addressFixture, $orderedFixtures);
+ $countryFixtureOrder = array_search($countryFixture, $orderedFixtures);
+ $stateFixtureOrder = array_search($stateFixture, $orderedFixtures);
+ $baseParentFixtureOrder = array_search($baseParentFixture, $orderedFixtures);
+
+ // Order of fixtures is not exact. We need to test, however, that dependencies are
+ // indeed satisfied
+
+ // BaseParentFixture1 has no dependencies, so it will always be first in this case
+ $this->assertEquals($baseParentFixtureOrder, 0);
+
+ $this->assertTrue(($contactFixtureOrder > $contactMethodFixtureOrder));
+ $this->assertTrue(($contactFixtureOrder > $addressFixtureOrder));
+ $this->assertTrue(($contactFixtureOrder > $countryFixtureOrder));
+ $this->assertTrue(($contactFixtureOrder > $stateFixtureOrder));
+ $this->assertTrue(($contactFixtureOrder > $contactMethodFixtureOrder));
+
+ $this->assertTrue(($addressFixtureOrder > $stateFixtureOrder));
+ $this->assertTrue(($addressFixtureOrder > $countryFixtureOrder));
+ }
+
+
+ /**
+ * @expectedException Doctrine\Common\DataFixtures\Exception\CircularReferenceException
+ */
+ public function test_orderFixturesByDependencies_circularReferencesMakeMethodThrowCircularReferenceException()
+ {
+ $loader = new Loader();
+
+ $loader->addFixture(new CircularReferenceFixture3);
+ $loader->addFixture(new CircularReferenceFixture);
+ $loader->addFixture(new CircularReferenceFixture2);
+
+ $orderedFixtures = $loader->getFixtures();
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function test_orderFixturesByDependencies_fixturesCantHaveItselfAsParent()
+ {
+ $loader = new Loader();
+
+ $loader->addFixture(new FixtureWithItselfAsParent);
+
+ $orderedFixtures = $loader->getFixtures();
+ }
+
+ public function test_inCaseThereAreFixturesOrderedByNumberAndByDependenciesBothOrdersAreExecuted()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new OrderedByNumberFixture1);
+ $loader->addFixture(new OrderedByNumberFixture3);
+ $loader->addFixture(new OrderedByNumberFixture2);
+ $loader->addFixture(new DependentFixture3);
+ $loader->addFixture(new DependentFixture1);
+ $loader->addFixture(new DependentFixture2);
+ $loader->addFixture(new BaseParentFixture1);
+
+ $orderedFixtures = $loader->getFixtures();
+
+ $this->assertEquals(7, count($orderedFixtures));
+
+ $this->assertTrue(array_shift($orderedFixtures) instanceof OrderedByNumberFixture1);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof OrderedByNumberFixture2);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof OrderedByNumberFixture3);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof BaseParentFixture1);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof DependentFixture1);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof DependentFixture2);
+ $this->assertTrue(array_shift($orderedFixtures) instanceof DependentFixture3);
+ }
+
+ /**
+ * @expectedException RuntimeException
+ */
+ public function test_inCaseAFixtureHasAnUnexistenDependencyOrIfItWasntLoaded_throwsException()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new FixtureWithUnexistentDependency);
+
+ $orderedFixtures = $loader->getFixtures();
+ }
+}
+
+class DependentFixture1 implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array( 'Doctrine\Tests\Common\DataFixtures\BaseParentFixture1' );
+ }
+}
+
+class DependentFixture2 implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array( 'Doctrine\Tests\Common\DataFixtures\DependentFixture1' );
+ }
+}
+
+class DependentFixture3 implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array( 'Doctrine\Tests\Common\DataFixtures\DependentFixture2' );
+ }
+}
+
+class BaseParentFixture1 implements FixtureInterface
+{
+ public function load($manager)
+ {}
+}
+
+class CountryFixture implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\BaseParentFixture1'
+ );
+ }
+}
+
+class StateFixture implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\BaseParentFixture1',
+ 'Doctrine\Tests\Common\DataFixtures\CountryFixture'
+ );
+ }
+}
+
+class AddressFixture implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\BaseParentFixture1',
+ 'Doctrine\Tests\Common\DataFixtures\CountryFixture',
+ 'Doctrine\Tests\Common\DataFixtures\StateFixture'
+ );
+ }
+}
+
+class ContactMethodFixture implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\BaseParentFixture1'
+ );
+ }
+}
+
+class ContactFixture implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\AddressFixture',
+ 'Doctrine\Tests\Common\DataFixtures\ContactMethodFixture'
+ );
+ }
+}
+
+class CircularReferenceFixture implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\CircularReferenceFixture3'
+ );
+ }
+}
+
+class CircularReferenceFixture2 implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\CircularReferenceFixture'
+ );
+ }
+}
+
+class CircularReferenceFixture3 implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\CircularReferenceFixture2'
+ );
+ }
+}
+
+class FixtureWithItselfAsParent implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\FixtureWithItselfAsParent'
+ );
+ }
+}
+
+class FixtureWithUnexistentDependency implements FixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array(
+ 'UnexistentDependency'
+ );
+ }
+}
+
+class FixtureImplementingBothOrderingInterfaces implements FixtureInterface, OrderedFixtureInterface, DependentFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getOrder()
+ {
+ return 1;
+ }
+
+ public function getDependencies()
+ {
+ return array(
+ 'Doctrine\Tests\Common\DataFixtures\FixtureWithItselfAsParent'
+ );
+ }
+}
+
+class OrderedByNumberFixture1 implements FixtureInterface, OrderedFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getOrder()
+ {
+ return 1;
+ }
+}
+
+class OrderedByNumberFixture2 implements FixtureInterface, OrderedFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getOrder()
+ {
+ return 5;
+ }
+}
+
+class OrderedByNumberFixture3 implements FixtureInterface, OrderedFixtureInterface
+{
+ public function load($manager)
+ {}
+
+ public function getOrder()
+ {
+ return 10;
+ }
+}
View
8 tests/Doctrine/Tests/Common/DataFixtures/LoaderTest.php
@@ -33,9 +33,9 @@ class LoaderTest extends BaseTest
public function testLoader()
{
$loader = new Loader();
- $loader->addFixture($this->getMock('Doctrine\Common\DataFixtures\FixtureInterface'));
- $loader->addFixture($this->getMock('Doctrine\Common\DataFixtures\FixtureInterface'));
- $loader->addFixture($this->getMock('Doctrine\Common\DataFixtures\SharedFixtureInterface'));
+ $loader->addFixture($this->getMock('Doctrine\Common\DataFixtures\FixtureInterface'), array(), array(), 'Mock1');
+ $loader->addFixture($this->getMock('Doctrine\Common\DataFixtures\FixtureInterface', array(), array(), 'Mock2'));
+ $loader->addFixture($this->getMock('Doctrine\Common\DataFixtures\SharedFixtureInterface', array(), array(), 'Mock3'));
$this->assertEquals(3, count($loader->getFixtures()));
@@ -44,4 +44,4 @@ public function testLoader()
$this->assertTrue($loader->isTransient('TestFixtures\NotAFixture'));
$this->assertFalse($loader->isTransient('TestFixtures\MyFixture1'));
}
-}
+}
View
2  tests/Doctrine/Tests/Common/DataFixtures/OrderedFixtureTest.php
@@ -86,4 +86,4 @@ class BaseFixture1 implements FixtureInterface
{
public function load($manager)
{}
-}
+}
View
69 tests/Doctrine/Tests/Common/DataFixtures/ReferenceRepositoryTest.php
@@ -20,6 +20,9 @@
namespace Doctrine\Tests\Common\DataFixtures;
use Doctrine\Common\DataFixtures\ReferenceRepository;
+use Doctrine\Common\DataFixtures\Event\Listener\ORMReferenceListener;
+use Doctrine\ORM\Tools\SchemaTool;
+use Doctrine\ORM\Proxy\Proxy;
require_once __DIR__.'/TestInit.php';
@@ -31,7 +34,7 @@
class ReferenceRepositoryTest extends BaseTest
{
const TEST_ENTITY_ROLE = 'Doctrine\Tests\Common\DataFixtures\TestEntity\Role';
-
+
public function testReferenceEntry()
{
$em = $this->getMockAnnotationReaderEntityManager();
@@ -39,20 +42,72 @@ public function testReferenceEntry()
$role->setName('admin');
$meta = $em->getClassMetadata(self::TEST_ENTITY_ROLE);
$meta->getReflectionProperty('id')->setValue($role, 1);
-
+
$referenceRepo = new ReferenceRepository($em);
$referenceRepo->addReference('test', $role);
-
+
$references = $referenceRepo->getReferences();
$this->assertEquals(1, count($references));
$this->assertArrayHasKey('test', $references);
$this->assertInstanceOf(self::TEST_ENTITY_ROLE, $references['test']);
}
-
- private function getMockReferenceRepository()
+
+ public function testReferenceIdentityPopulation()
{
- return $this->getMockBuilder('Doctrine\Common\DataFixtures\ReferenceRepository')
- ->setConstructorArgs(array($this->em))
+ $em = $this->getMockSqliteEntityManager();
+ $referenceRepository = $this->getMockBuilder('Doctrine\Common\DataFixtures\ReferenceRepository')
+ ->setConstructorArgs(array($em))
->getMock();
+ $em->getEventManager()->addEventSubscriber(
+ new ORMReferenceListener($referenceRepository)
+ );
+ $schemaTool = new SchemaTool($em);
+ $schemaTool->dropSchema(array());
+ $schemaTool->createSchema(array(
+ $em->getClassMetadata(self::TEST_ENTITY_ROLE)
+ ));
+
+ $referenceRepository->expects($this->once())
+ ->method('addReference')
+ ->with('admin-role');
+
+ $referenceRepository->expects($this->once())
+ ->method('getReferenceName')
+ ->will($this->returnValue('admin-role'));
+
+ $referenceRepository->expects($this->once())
+ ->method('setReferenceIdentity')
+ ->with('admin-role', array('id' => 1));
+
+ $roleFixture = new TestFixtures\RoleFixture;
+ $roleFixture->setReferenceRepository($referenceRepository);
+
+ $roleFixture->load($em);
+ }
+
+ public function testReferenceReconstruction()
+ {
+ $em = $this->getMockSqliteEntityManager();
+ $referenceRepository = new ReferenceRepository($em);
+ $em->getEventManager()->addEventSubscriber(
+ new ORMReferenceListener($referenceRepository)
+ );
+ $schemaTool = new SchemaTool($em);
+ $schemaTool->dropSchema(array());
+ $schemaTool->createSchema(array(
+ $em->getClassMetadata(self::TEST_ENTITY_ROLE)
+ ));
+ $roleFixture = new TestFixtures\RoleFixture;
+ $roleFixture->setReferenceRepository($referenceRepository);
+
+ $roleFixture->load($em);
+ // first test against managed state
+ $ref = $referenceRepository->getReference('admin-role');
+ $this->assertFalse($ref instanceof Proxy);
+
+ // now test reference reconstruction from identity
+ $em->clear();
+ $ref = $referenceRepository->getReference('admin-role');
+ $this->assertTrue($ref instanceof Proxy);
}
}
View
7 tests/Doctrine/Tests/Common/DataFixtures/TestFixtures/RoleFixture.php
@@ -20,13 +20,8 @@ public function load($manager)
$adminRole = new Role();
$adminRole->setName('admin');
- $anonymousRole = new Role;
- $anonymousRole->setName('anonymous');
-
$manager->persist($adminRole);
- $manager->persist($anonymousRole);
- $manager->flush();
-
$this->referenceRepository->addReference('admin-role', $adminRole);
+ $manager->flush();
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.