Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

New ProvideFixtureInterface #74

Closed
wants to merge 13 commits into from

5 participants

@djlambert

I was thinking it would be much easier to define fixture dependencies than manually setting an order. After looking at the code I found there was an existing DependentFixtureInterface that was undocumented. The new ProvideFixtureInterface allows the use of a simple string instead of the full fixture class name in the array returned from getDependencies. Documentation is also included.

The test ProxyReferenceRepositoryTest::testReferenceReconstruction is failing, but it's also failing on master.

@stof
Collaborator

is this new ProvidesFixtureInterface really needed ? when you depend on classes in the same namespace, you can use _NAMESPACE__ to avoid duplicating the namespace, and it avoids issues. Once you start adding a ProvidesFixtureInterface, you also need to add some checks about the uniqueness of the provided alias to avoid ambiguity (which is not the case in your patch and will lead to unexpected behaviors).

Could you send the documentation for the existing feature in a separate pull request so that it can be merged separately ?

@djlambert

In the project I'm working on I've defined the entities in namespaces according to inheritance or relationships like:

MyNamespace\Media
MyNamespace\Media\Audio
MyNamespace\Media\Video
MyNamespace\User

I've setup my fixtures to follow the same scheme

MyFixtures\LoadMedia
MyFixtures\Media\LoadAudio
MyFixtures\Media\LoadVideo
MyFixtures\LoadUser

So in LoadAudio I can't use __NAMESPACE__ to reference LoadUser. I did add a check in Loader::addFixture to make sure the alias wasn't already defined. Are there additional checks that would be needed?

Initially I was going to create a base fixture with the aliases and then have my fixtures extend that, but I thought others might find the feature useful and added the interface.

@guilhermeblanco

Please rebase your PR since you have 2 different requests here (docs and code changes).
I merged the documentation one, but to move this one further, you need to rebase it.

@djlambert

Rebased.

lib/Doctrine/Common/DataFixtures/Loader.php
@@ -140,6 +147,24 @@ public function addFixture(FixtureInterface $fixture)
$this->orderFixturesByDependencies = true;
}
+ if ($fixture instanceof ProvidesFixtureInterface) {
+ $fixtureProvides = $fixture->getProvides();
+ if (!empty($fixtureProvides) && is_string($fixtureProvides)) {
+ if (isset($this->fixtureProvides[$fixtureProvides])) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" can\'t provide "%s", it is already provided by "%s".',
+ $fixtureClass,
+ $fixtureProvides,
+ $this->fixtureProvides[$fixtureProvides]));
@stof Collaborator
stof added a note

Please fix the indentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/Common/DataFixtures/Loader.php
@@ -140,6 +147,24 @@ public function addFixture(FixtureInterface $fixture)
$this->orderFixturesByDependencies = true;
}
+ if ($fixture instanceof ProvidesFixtureInterface) {
+ $fixtureProvides = $fixture->getProvides();
+ if (!empty($fixtureProvides) && is_string($fixtureProvides)) {
+ if (isset($this->fixtureProvides[$fixtureProvides])) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" can\'t provide "%s", it is already provided by "%s".',
+ $fixtureClass,
+ $fixtureProvides,
+ $this->fixtureProvides[$fixtureProvides]));
+ } else {
+ $this->fixtureProvides[$fixture->getProvides()] = $fixtureClass;
@stof Collaborator
stof added a note

why not reusing the variable ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
lib/Doctrine/Common/DataFixtures/Loader.php
@@ -140,6 +147,24 @@ public function addFixture(FixtureInterface $fixture)
$this->orderFixturesByDependencies = true;
}
+ if ($fixture instanceof ProvidesFixtureInterface) {
+ $fixtureProvides = $fixture->getProvides();
+ if (!empty($fixtureProvides) && is_string($fixtureProvides)) {
@stof Collaborator
stof added a note

please use the opposite condition and throw the exception, which then avoids uisng else for the other case

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...rine/Common/DataFixtures/ProvidesFixtureInterface.php
((22 lines not shown))
+/**
+ * ProvidesFixtureInterface can be implemented to use
+ * string values instead of class names by fixtures to
+ * depend on other fixtures
+ *
+ * @author Derek J. Lambert <dlambert@dereklambert.com>
+ */
+interface ProvidesFixtureInterface
+{
+ /**
+ * This method must return a string representing what the
+ * fixture provides to dependent classes
+ *
+ * @return string
+ */
+ public function getProvides();
@stof Collaborator
stof added a note

the naming is weird as it returns a single alias for the fixture class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ine/Tests/Common/DataFixtures/ProvidesFixtureTest.php
((33 lines not shown))
+ *
+ * @author Derek J. Lambert <dlambert@dereklambert.com>
+ */
+class ProvideDependentFixtureTest extends BaseTest
+{
+ public function testOrderFixturesByProvidesDependencies()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new ProvidesFixture4);
+ $loader->addFixture(new ProvidesFixture2);
+ $loader->addFixture(new ProvidesFixture3);
+ $loader->addFixture(new ProvidesFixture1);
+
+ $orderedFixtures = $loader->getFixtures();
+
+ $this->assertEquals(4, count($orderedFixtures));
@stof Collaborator
stof added a note

you should use assertCount

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ine/Tests/Common/DataFixtures/ProvidesFixtureTest.php
((14 lines not shown))
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Tests\Common\DataFixtures;
+
+use Doctrine\Common\DataFixtures\Loader;
+use Doctrine\Common\DataFixtures\ProvidesFixtureInterface;
+use Doctrine\Common\DataFixtures\DependentFixtureInterface;
+use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
+use Doctrine\Common\DataFixtures\FixtureInterface;
+use Doctrine\Common\Persistence\ObjectManager;
+
+require_once __DIR__.'/TestInit.php';
@stof Collaborator
stof added a note

IIRC, this is not needed anymore

Which part? I've removed OrderedFixtureInterface.

@stof Collaborator
stof added a note

The require_once call, which is the line on which I commented

Sorry, I was thinking the comment applied to the whole section of code. I didn't realize the last line was the one being commented on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...ine/Tests/Common/DataFixtures/ProvidesFixtureTest.php
((35 lines not shown))
+ */
+class ProvideDependentFixtureTest extends BaseTest
+{
+ public function testOrderFixturesByProvidesDependencies()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new ProvidesFixture4);
+ $loader->addFixture(new ProvidesFixture2);
+ $loader->addFixture(new ProvidesFixture3);
+ $loader->addFixture(new ProvidesFixture1);
+
+ $orderedFixtures = $loader->getFixtures();
+
+ $this->assertEquals(4, count($orderedFixtures));
+
+ $this->assertTrue(array_shift($orderedFixtures) instanceof ProvidesFixture1);
@stof Collaborator
stof added a note

you should use assertInstanceOf instead of assertTrue. It gives a better error message when failing than Failed asserting that boolean<false> is true.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@peterhorne

@djlambert I was just thinking that this feature would be really useful, thanks! Hope to see this PR accepted soon :)

@lavoiesl
Collaborator

It seems that this PR is obsolete since we now use DependentFixtureInterface, can we close this?

@lavoiesl lavoiesl closed this
@lavoiesl lavoiesl added the Duplicate label
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
31 README.md
@@ -160,6 +160,37 @@ Provide an array of fixture class names:
{}
}
+or define alias names with ProvideFixtureInterface:
+
+ namespace MyDataFixtures;
+
+ use Doctrine\Common\DataFixtures\AbstractFixture;
+ use Doctrine\Common\DataFixtures\DependentFixtureInterface;
+ use Doctrine\Common\DataFixtures\ProvideFixtureInterface;
+ use Doctrine\Common\Persistence\ObjectManager;
+
+ class MyFixture extends AbstractFixture implements DependentFixtureInterface
+ {
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getDepends()
+ {
+ return array('Other'); // fixture classes or provide aliases fixture is dependent on
+ }
+ }
+
+ class MyOtherFixture extends AbstractFixture implements ProvideFixtureInterface
+ {
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getProvide()
+ {
+ return 'Other';
+ }
+ }
+
**Notice** the ordering is relevant to Loader class.
## Running the tests:
View
82 lib/Doctrine/Common/DataFixtures/Loader.php
@@ -49,7 +49,7 @@ class Loader
* @var boolean
*/
private $orderFixturesByNumber = false;
-
+
/**
* Determines if we must order fixtures by its dependencies
*
@@ -65,6 +65,13 @@ class Loader
private $fileExtension = '.php';
/**
+ * Array for provide string to class name lookup
+ *
+ * @var array
+ */
+ private $fixtureProvides = array();
+
+ /**
* Find fixtures classes in a given directory and load them.
*
* @param string $dir Directory to find fixture classes in.
@@ -93,11 +100,11 @@ public function loadFromDirectory($dir)
$includedFiles[] = $sourceFile;
}
$declared = get_declared_classes();
-
+
foreach ($declared as $className) {
$reflClass = new \ReflectionClass($className);
$sourceFile = $reflClass->getFileName();
-
+
if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
$fixture = new $className;
$fixtures[] = $fixture;
@@ -130,8 +137,8 @@ public function addFixture(FixtureInterface $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),
+ throw new \InvalidArgumentException(sprintf('Class "%s" can\'t implement "%s" and "%s" at the same time.',
+ $fixtureClass,
'OrderedFixtureInterface',
'DependentFixtureInterface'));
} elseif ($fixture instanceof OrderedFixtureInterface) {
@@ -140,6 +147,25 @@ public function addFixture(FixtureInterface $fixture)
$this->orderFixturesByDependencies = true;
}
+ if ($fixture instanceof ProvideFixtureInterface) {
+ $fixtureProvide = $fixture->getProvide();
+
+ if (empty($fixtureProvide) || !is_string($fixtureProvide)) {
+ throw new \InvalidArgumentException(sprintf('Method "%s" in class "%s" must return a non empty string.',
+ 'getProvide',
+ $fixtureClass));
+ }
+
+ if (isset($this->fixtureProvides[$fixtureProvide])) {
+ throw new \InvalidArgumentException(sprintf('Class "%s" can\'t provide "%s", it is already provided by "%s".',
+ $fixtureClass,
+ $fixtureProvide,
+ $this->fixtureProvides[$fixtureProvide]));
+ }
+
+ $this->fixtureProvides[$fixtureProvide] = $fixtureClass;
+ }
+
$this->fixtures[$fixtureClass] = $fixture;
}
}
@@ -158,7 +184,7 @@ public function getFixtures()
if ($this->orderFixturesByDependencies) {
$this->orderFixturesByDependencies();
}
-
+
if (!$this->orderFixturesByNumber && !$this->orderFixturesByDependencies) {
$this->orderedFixtures = $this->fixtures;
}
@@ -183,7 +209,7 @@ public function isTransient($className)
/**
* Orders fixtures by number
- *
+ *
* @todo maybe there is a better way to handle reordering
* @return void
*/
@@ -204,22 +230,22 @@ private function orderFixturesByNumber()
return 0;
});
}
-
-
+
+
/**
* Orders fixtures by dependencies
- *
+ *
* @return void
*/
private function orderFixturesByDependencies()
{
$sequenceForClasses = array();
- // If fixtures were already ordered by number then we need
+ // 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
+ // will handle all fixtures which are not instances of
// OrderedFixtureInterface
if ($this->orderFixturesByNumber) {
$count = count($this->orderedFixtures);
@@ -239,7 +265,13 @@ private function orderFixturesByDependencies()
continue;
} elseif ($fixture instanceof DependentFixtureInterface) {
$dependenciesClasses = $fixture->getDependencies();
-
+
+ foreach ($dependenciesClasses as &$dependency) {
+ if (isset($this->fixtureProvides[$dependency])) {
+ $dependency = $this->fixtureProvides[$dependency];
+ }
+ }
+
$this->validateDependencies($dependenciesClasses);
if (!is_array($dependenciesClasses) || empty($dependenciesClasses)) {
@@ -249,7 +281,7 @@ private function orderFixturesByDependencies()
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 {
@@ -261,7 +293,7 @@ private function orderFixturesByDependencies()
// 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];
@@ -270,29 +302,29 @@ private function orderFixturesByDependencies()
if (count($unsequencedDependencies) === 0) {
$sequenceForClasses[$class] = $sequence++;
- }
+ }
}
-
+
$lastCount = $count;
}
$orderedFixtures = array();
-
- // If there're fixtures unsequenced left and they couldn't be sequenced,
+
+ // 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
+ // If fixtures were ordered
$orderedFixtures[] = $this->fixtures[$class];
}
}
@@ -303,7 +335,7 @@ private function orderFixturesByDependencies()
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 should be added in fixture loader first.', $class));
@@ -322,11 +354,15 @@ private function getUnsequencedClasses($sequences, $classes = null)
}
foreach ($classes as $class) {
+ if (isset($this->fixtureProvides[$class])) {
+ $class = $this->fixtureProvides[$class];
+ }
+
if ($sequences[$class] === -1) {
$unsequencedClasses[] = $class;
}
}
return $unsequencedClasses;
- }
+ }
}
View
38 lib/Doctrine/Common/DataFixtures/ProvideFixtureInterface.php
@@ -0,0 +1,38 @@
+<?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 MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Common\DataFixtures;
+
+/**
+ * ProvideFixtureInterface can be implemented to use
+ * string values instead of class names by fixtures to
+ * depend on other fixtures
+ *
+ * @author Derek J. Lambert <dlambert@dereklambert.com>
+ */
+interface ProvideFixtureInterface
+{
+ /**
+ * This method must return a string representing what the
+ * fixture provides to dependent classes
+ *
+ * @return string
+ */
+ public function getProvide();
+}
View
148 tests/Doctrine/Tests/Common/DataFixtures/ProvideFixtureTest.php
@@ -0,0 +1,148 @@
+<?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 MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\Tests\Common\DataFixtures;
+
+use Doctrine\Common\DataFixtures\Loader;
+use Doctrine\Common\DataFixtures\ProvideFixtureInterface;
+use Doctrine\Common\DataFixtures\DependentFixtureInterface;
+use Doctrine\Common\DataFixtures\FixtureInterface;
+use Doctrine\Common\Persistence\ObjectManager;
+
+/**
+ * Test fixture ordering by provided dependencies.
+ *
+ * @author Derek J. Lambert <dlambert@dereklambert.com>
+ */
+class ProvideDependentFixtureTest extends BaseTest
+{
+ public function testOrderFixturesByProvidesDependencies()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new ProvideFixture4);
+ $loader->addFixture(new ProvideFixture2);
+ $loader->addFixture(new ProvideFixture3);
+ $loader->addFixture(new ProvideFixture1);
+
+ $orderedFixtures = $loader->getFixtures();
+
+ $this->assertCount(4, $orderedFixtures);
+
+ $this->assertInstanceOf('Doctrine\Tests\Common\DataFixtures\ProvideFixture1', array_shift($orderedFixtures));
+ $this->assertInstanceOf('Doctrine\Tests\Common\DataFixtures\ProvideFixture2', array_shift($orderedFixtures));
+ $this->assertInstanceOf('Doctrine\Tests\Common\DataFixtures\ProvideFixture3', array_shift($orderedFixtures));
+ $this->assertInstanceOf('Doctrine\Tests\Common\DataFixtures\ProvideFixture4', array_shift($orderedFixtures));
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testDuplicateProvidesException()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new ProvideFixture1);
+ $loader->addFixture(new DuplicateProvideFixture);
+ }
+
+ /**
+ * @expectedException InvalidArgumentException
+ */
+ public function testBadProvidesException()
+ {
+ $loader = new Loader();
+ $loader->addFixture(new ProvideFixture1);
+ $loader->addFixture(new BadProvideFixture);
+ }
+}
+
+class ProvideFixture1 implements FixtureInterface, ProvideFixtureInterface
+{
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getProvide()
+ {
+ return 'ProvideFixture1';
+ }
+}
+
+class ProvideFixture2 implements FixtureInterface, DependentFixtureInterface, ProvideFixtureInterface
+{
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array('ProvideFixture1' );
+ }
+
+ public function getProvide()
+ {
+ return 'ProvideFixture2';
+ }
+}
+
+class ProvideFixture3 implements FixtureInterface, DependentFixtureInterface, ProvideFixtureInterface
+{
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array('ProvideFixture2' );
+ }
+
+ public function getProvide()
+ {
+ return 'ProvideFixture3';
+ }
+}
+
+class ProvideFixture4 implements FixtureInterface, DependentFixtureInterface
+{
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getDependencies()
+ {
+ return array( 'ProvideFixture3' );
+ }
+}
+
+class DuplicateProvideFixture implements FixtureInterface, ProvideFixtureInterface
+{
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getProvide()
+ {
+ return 'ProvideFixture1';
+ }
+}
+
+class BadProvideFixture implements FixtureInterface, ProvideFixtureInterface
+{
+ public function load(ObjectManager $manager)
+ {}
+
+ public function getProvide()
+ {
+ return '';
+ }
+}
Something went wrong with that request. Please try again.