Skip to content

Commit

Permalink
Re-working fixtures loading to load tagged services
Browse files Browse the repository at this point in the history
Also fixing a bug where getDependencies()

Basically, currently getDependencies() won't work if any of your classes
have required constructor args. We've added a clear error message for
this situation.

Also added forwards-compat for the soon-to-be-released
doctrine/data-fixtures
version 1.3, which will fix the above problem.
  • Loading branch information
weaverryan committed Nov 28, 2017
1 parent 74b8cc7 commit ee5eb61
Show file tree
Hide file tree
Showing 20 changed files with 582 additions and 140 deletions.
39 changes: 39 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
language: php
sudo: false
cache:
directories:
- $HOME/.composer/cache/files

matrix:
fast_finish: true
include:
# Minimum supported PHP and Symfony version
- php: 5.5
env: DEPENDENCIES="minimum"

# Test the latest stable release
- php: 5.5
- php: 5.6
- php: 7.0
- php: 7.1
- php: 7.2

# Test LTS version we support
- php: 7.2
env: DEPENDENCIES="symfony/lts:v3"

- php: 7.2
env: DEPENDENCIES="beta"

before_install:
- if [ "$DEPENDENCIES" = "minimum" ]; then COMPOSER_FLAGS="--prefer-stable --prefer-lowest"; fi;
- if [ "$DEPENDENCIES" = "beta" ]; then composer config minimum-stability beta; fi;
- if [[ $DEPENDENCIES == *"/"* ]]; then composer require --no-update $DEPENDENCIES; fi;

install:
# To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355
- if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
- travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction

script:
- ./vendor/bin/simple-phpunit
42 changes: 14 additions & 28 deletions Command/LoadDataFixturesDoctrineCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
namespace Doctrine\Bundle\FixturesBundle\Command;

use Doctrine\Bundle\DoctrineBundle\Command\DoctrineCommand;
use Doctrine\Bundle\FixturesBundle\Loader\SymfonyFixturesLoader;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\DBAL\Sharding\PoolingShardConnection;
use InvalidArgumentException;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader as DataFixturesLoader;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
Expand All @@ -33,24 +33,30 @@
*/
class LoadDataFixturesDoctrineCommand extends DoctrineCommand
{
private $fixturesLoader;

public function __construct(SymfonyFixturesLoader $fixturesLoader)
{
parent::__construct();

$this->fixturesLoader = $fixturesLoader;
}

protected function configure()
{
$this
->setName('doctrine:fixtures:load')
->setDescription('Load data fixtures to your database.')
->addOption('fixtures', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The directory to load data fixtures from.')
->addOption('append', null, InputOption::VALUE_NONE, 'Append the data fixtures instead of deleting all data from the database first.')
->addOption('em', null, InputOption::VALUE_REQUIRED, 'The entity manager to use for this command.')
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command.')
->addOption('purge-with-truncate', null, InputOption::VALUE_NONE, 'Purge data by using a database-level TRUNCATE statement')
->setHelp(<<<EOT
The <info>%command.name%</info> command loads data fixtures from your bundles:
The <info>%command.name%</info> command loads data fixtures from your application:
<info>php %command.full_name%</info>
You can also optionally specify the path to fixtures with the <info>--fixtures</info> option:
<info>php %command.full_name% --fixtures=/path/to/fixtures1 --fixtures=/path/to/fixtures2</info>
Fixtures are services that are tagged with doctrine.fixture.orm.
If you want to append the fixtures instead of flushing the database first you can use the <info>--append</info> option:
Expand Down Expand Up @@ -84,30 +90,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
$em->getConnection()->connect($input->getOption('shard'));
}

$dirOrFile = $input->getOption('fixtures');
if ($dirOrFile) {
$paths = is_array($dirOrFile) ? $dirOrFile : array($dirOrFile);
} else {
/** @var $kernel \Symfony\Component\HttpKernel\KernelInterface */
$kernel = $this->getApplication()->getKernel();
$paths = array($kernel->getRootDir().'/DataFixtures/ORM');
foreach ($kernel->getBundles() as $bundle) {
$paths[] = $bundle->getPath().'/DataFixtures/ORM';
}
}

$loader = new DataFixturesLoader($this->getContainer());
foreach ($paths as $path) {
if (is_dir($path)) {
$loader->loadFromDirectory($path);
} elseif (is_file($path)) {
$loader->loadFromFile($path);
}
}
$fixtures = $loader->getFixtures();
$fixtures = $this->fixturesLoader->getFixtures();
if (!$fixtures) {
throw new InvalidArgumentException(
sprintf('Could not find any fixtures to load in: %s', "\n\n- ".implode("\n- ", $paths))
'Could not find any fixture services to load.'
);
}
$purger = new ORMPurger($em);
Expand Down
37 changes: 37 additions & 0 deletions DependencyInjection/CompilerPass/FixturesCompilerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the Doctrine Fixtures Bundle
*
* The code was originally distributed inside the Symfony framework.
*
* (c) Fabien Potencier <fabien@symfony.com>
* (c) Doctrine Project
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Doctrine\Bundle\FixturesBundle\DependencyInjection\CompilerPass;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;

/**
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
final class FixturesCompilerPass implements CompilerPassInterface
{
const FIXTURE_TAG = 'doctrine.fixture.orm';

public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('doctrine.fixtures.loader');
$taggedServices = $container->findTaggedServiceIds(self::FIXTURE_TAG);

foreach ($taggedServices as $serviceId => $tags) {
$definition->addMethodCall('addFixture', [new Reference($serviceId)]);
}
}
}
5 changes: 5 additions & 0 deletions DependencyInjection/DoctrineFixturesExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

namespace Doctrine\Bundle\FixturesBundle\DependencyInjection;

use Doctrine\Bundle\FixturesBundle\DependencyInjection\CompilerPass\FixturesCompilerPass;
use Doctrine\Bundle\FixturesBundle\ORMFixtureInterface;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
Expand All @@ -29,5 +31,8 @@ public function load(array $configs, ContainerBuilder $container)
$loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources/config'));

$loader->load('services.xml');

$container->registerForAutoconfiguration(ORMFixtureInterface::class)
->addTag(FixturesCompilerPass::FIXTURE_TAG);
}
}
7 changes: 7 additions & 0 deletions DoctrineFixturesBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

namespace Doctrine\Bundle\FixturesBundle;

use Doctrine\Bundle\FixturesBundle\DependencyInjection\CompilerPass\FixturesCompilerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
Expand All @@ -24,4 +26,9 @@
*/
class DoctrineFixturesBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new FixturesCompilerPass());
}

}
26 changes: 0 additions & 26 deletions EmptyFixture.php

This file was deleted.

11 changes: 1 addition & 10 deletions Fixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,6 @@
*
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
abstract class Fixture extends AbstractFixture implements ContainerAwareInterface, DependentFixtureInterface
abstract class Fixture extends AbstractFixture implements ORMFixtureInterface
{
use ContainerAwareTrait;

public function getDependencies()
{
// 'EmptyFixture' is a fixture class that loads no data. It's required
// because Doctrine doesn't allow to return an empty array in this method
// See https://github.com/doctrine/data-fixtures/pull/252
return array(EmptyFixture::class);
}
}
80 changes: 80 additions & 0 deletions Loader/SymfonyFixturesLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

/*
* This file is part of the Doctrine Fixtures Bundle.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Doctrine\Bundle\FixturesBundle\Loader;

use Doctrine\Bundle\FixturesBundle\DependencyInjection\CompilerPass\FixturesCompilerPass;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Loader;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;

/**
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
final class SymfonyFixturesLoader extends ContainerAwareLoader
{
public function addFixture(FixtureInterface $fixture)
{
// see https://github.com/doctrine/data-fixtures/pull/274
// this is to give a clear error if you do not have this version
if (!method_exists(Loader::class, 'createFixture')) {
$this->checkForNonInstantiableFixtures($fixture);
}

parent::addFixture($fixture);
}

/**
* Overridden to not allow new fixture classes to be instantiated.
*/
protected function createFixture($class)
{
try {
/*
* We don't actually need to create the fixture. We just
* return the one that already exists.
*/
return $this->getFixture($class);
} catch (\InvalidArgumentException $e) {
throw new \LogicException(sprintf('The "%s" fixture class is trying to be loaded, but is not available. Make sure this class is defined as a service and tagged with "%s".', $class, FixturesCompilerPass::FIXTURE_TAG));
}
}

/**
* For doctrine/data-fixtures 1.2 or lower, this detects an unsupported
* feature with DependentFixtureInterface so that we can throw a
* clear exception.
*
* @param FixtureInterface $fixture
* @throws \Exception
*/
private function checkForNonInstantiableFixtures(FixtureInterface $fixture)
{
if (!$fixture instanceof DependentFixtureInterface) {
return;
}

foreach ($fixture->getDependencies() as $dependency) {
if (!class_exists($dependency)) {
continue;
}

if (!method_exists($dependency, '__construct')) {
continue;
}

$reflMethod = new \ReflectionMethod($dependency, '__construct');
foreach ($reflMethod->getParameters() as $param) {
if (!$param->isOptional()) {
throw new \LogicException(sprintf('The getDependencies() method returned a class (%s) that has required constructor arguments. Upgrade to "doctrine/data-fixtures" version 1.3 or higher to support this.', $dependency));
}
}
}
}
}
12 changes: 12 additions & 0 deletions ORMFixtureInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Doctrine\Bundle\FixturesBundle;

use Doctrine\Common\DataFixtures\FixtureInterface;

/**
* Marks your fixtures that are specifically for the ORM.
*/
interface ORMFixtureInterface extends FixtureInterface
{
}
4 changes: 4 additions & 0 deletions Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@

<!-- commands -->
<service id="doctrine.fixtures_load_command" class="Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand">
<argument type="service" id="doctrine.fixtures.loader" />
<tag name="console.command" command="doctrine:fixtures:load" />
</service>

<service id="doctrine.fixtures.loader" class="Doctrine\Bundle\FixturesBundle\Loader\SymfonyFixturesLoader" public="false">
<argument type="service" id="service_container" />
</service>
</services>
</container>
Loading

0 comments on commit ee5eb61

Please sign in to comment.