123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- <?php
- namespace Doctrine\Common\DataFixtures;
- use Doctrine\Common\DataFixtures\Exception\CircularReferenceException;
- /**
- * Class responsible for loading data fixture classes.
- *
- * @author Jonathan H. Wage <jonwage@gmail.com>
- */
- class Loader
- {
- /**
- * Array of fixture object instances to execute.
- *
- * @var array
- */
- private $fixtures = [];
- /**
- * Array of ordered fixture object instances.
- *
- * @var array
- */
- 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
- */
- private $fileExtension = '.php';
- /**
- * Find fixtures classes in a given directory and load them.
- *
- * @param string $dir Directory to find fixture classes in.
- * @return array $fixtures Array of loaded fixture object instances.
- */
- public function loadFromDirectory($dir)
- {
- if (!is_dir($dir)) {
- throw new \InvalidArgumentException(sprintf('"%s" does not exist', $dir));
- }
- $iterator = new \RecursiveIteratorIterator(
- new \RecursiveDirectoryIterator($dir),
- \RecursiveIteratorIterator::LEAVES_ONLY
- );
- return $this->loadFromIterator($iterator);
- }
- /**
- * Find fixtures classes in a given file and load them.
- *
- * @param string $fileName File to find fixture classes in.
- * @return array $fixtures Array of loaded fixture object instances.
- */
- public function loadFromFile($fileName)
- {
- if (!is_readable($fileName)) {
- throw new \InvalidArgumentException(sprintf('"%s" does not exist or is not readable', $fileName));
- }
- $iterator = new \ArrayIterator([new \SplFileInfo($fileName)]);
- return $this->loadFromIterator($iterator);
- }
- /**
- * Has fixture?
- *
- * @param FixtureInterface $fixture
- *
- * @return boolean
- */
- public function hasFixture($fixture)
- {
- return isset($this->fixtures[get_class($fixture)]);
- }
- /**
- * Get a specific fixture instance
- *
- * @param string $className
- * @return FixtureInterface
- */
- public function getFixture($className)
- {
- if (!isset($this->fixtures[$className])) {
- throw new \InvalidArgumentException(sprintf(
- '"%s" is not a registered fixture',
- $className
- ));
- }
- return $this->fixtures[$className];
- }
- /**
- * Add a fixture object instance to the loader.
- *
- * @param FixtureInterface $fixture
- */
- public function addFixture(FixtureInterface $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'));
- }
- $this->fixtures[$fixtureClass] = $fixture;
- if ($fixture instanceof OrderedFixtureInterface) {
- $this->orderFixturesByNumber = true;
- } elseif ($fixture instanceof DependentFixtureInterface) {
- $this->orderFixturesByDependencies = true;
- foreach($fixture->getDependencies() as $class) {
- if (class_exists($class)) {
- $this->addFixture($this->createFixture($class));
- }
- }
- }
- }
- }
- /**
- * Returns the array of data fixtures to execute.
- *
- * @return array $fixtures
- */
- public function getFixtures()
- {
- $this->orderedFixtures = [];
- if ($this->orderFixturesByNumber) {
- $this->orderFixturesByNumber();
- }
- if ($this->orderFixturesByDependencies) {
- $this->orderFixturesByDependencies();
- }
-
- if (!$this->orderFixturesByNumber && !$this->orderFixturesByDependencies) {
- $this->orderedFixtures = $this->fixtures;
- }
- return $this->orderedFixtures;
- }
- /**
- * Check if a given fixture is transient and should not be considered a data fixtures
- * class.
- *
- * @return boolean
- */
- public function isTransient($className)
- {
- $rc = new \ReflectionClass($className);
- if ($rc->isAbstract()) return true;
- $interfaces = class_implements($className);
- return in_array(FixtureInterface::class, $interfaces) ? false : true;
- }
- /**
- * Creates the fixture object from the class.
- *
- * @param string $class
- * @return FixtureInterface
- */
- protected function createFixture($class)
- {
- return new $class();
- }
- /**
- * Orders fixtures by number
- *
- * @todo maybe there is a better way to handle reordering
- * @return void
- */
- private function orderFixturesByNumber()
- {
- $this->orderedFixtures = $this->fixtures;
- usort($this->orderedFixtures, function($a, $b) {
- if ($a instanceof OrderedFixtureInterface && $b instanceof OrderedFixtureInterface) {
- if ($a->getOrder() === $b->getOrder()) {
- return 0;
- }
- return $a->getOrder() < $b->getOrder() ? -1 : 1;
- } elseif ($a instanceof OrderedFixtureInterface) {
- return $a->getOrder() === 0 ? 0 : 1;
- } elseif ($b instanceof OrderedFixtureInterface) {
- return $b->getOrder() === 0 ? 0 : -1;
- }
- return 0;
- });
- }
-
-
- /**
- * Orders fixtures by dependencies
- *
- * @return void
- */
- private function orderFixturesByDependencies()
- {
- $sequenceForClasses = [];
- // 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 = [];
-
- // 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 = array_merge($this->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 should be added in fixture loader first.', $class));
- }
- }
- return true;
- }
- private function getUnsequencedClasses($sequences, $classes = null)
- {
- $unsequencedClasses = [];
- if (is_null($classes)) {
- $classes = array_keys($sequences);
- }
- foreach ($classes as $class) {
- if ($sequences[$class] === -1) {
- $unsequencedClasses[] = $class;
- }
- }
- return $unsequencedClasses;
- }
- /**
- * Load fixtures from files contained in iterator.
- *
- * @param \Iterator $iterator Iterator over files from which fixtures should be loaded.
- * @return array $fixtures Array of loaded fixture object instances.
- */
- private function loadFromIterator(\Iterator $iterator)
- {
- $includedFiles = [];
- foreach ($iterator as $file) {
- if (($fileName = $file->getBasename($this->fileExtension)) == $file->getBasename()) {
- continue;
- }
- $sourceFile = realpath($file->getPathName());
- require_once $sourceFile;
- $includedFiles[] = $sourceFile;
- }
- $fixtures = [];
- $declared = get_declared_classes();
- // Make the declared classes order deterministic
- sort($declared);
- foreach ($declared as $className) {
- $reflClass = new \ReflectionClass($className);
- $sourceFile = $reflClass->getFileName();
- if (in_array($sourceFile, $includedFiles) && ! $this->isTransient($className)) {
- $fixture = $this->createFixture($className);
- $fixtures[] = $fixture;
- $this->addFixture($fixture);
- }
- }
- return $fixtures;
- }
- }
|