Skip to content

Commit ee5eb61

Browse files
committed
Re-working fixtures loading to load tagged services
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.
1 parent 74b8cc7 commit ee5eb61

20 files changed

+582
-140
lines changed

.travis.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
language: php
2+
sudo: false
3+
cache:
4+
directories:
5+
- $HOME/.composer/cache/files
6+
7+
matrix:
8+
fast_finish: true
9+
include:
10+
# Minimum supported PHP and Symfony version
11+
- php: 5.5
12+
env: DEPENDENCIES="minimum"
13+
14+
# Test the latest stable release
15+
- php: 5.5
16+
- php: 5.6
17+
- php: 7.0
18+
- php: 7.1
19+
- php: 7.2
20+
21+
# Test LTS version we support
22+
- php: 7.2
23+
env: DEPENDENCIES="symfony/lts:v3"
24+
25+
- php: 7.2
26+
env: DEPENDENCIES="beta"
27+
28+
before_install:
29+
- if [ "$DEPENDENCIES" = "minimum" ]; then COMPOSER_FLAGS="--prefer-stable --prefer-lowest"; fi;
30+
- if [ "$DEPENDENCIES" = "beta" ]; then composer config minimum-stability beta; fi;
31+
- if [[ $DEPENDENCIES == *"/"* ]]; then composer require --no-update $DEPENDENCIES; fi;
32+
33+
install:
34+
# To be removed when this issue will be resolved: https://github.com/composer/composer/issues/5355
35+
- if [[ "$COMPOSER_FLAGS" == *"--prefer-lowest"* ]]; then travis_retry composer update --prefer-dist --no-interaction --prefer-stable --quiet; fi
36+
- travis_retry composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction
37+
38+
script:
39+
- ./vendor/bin/simple-phpunit

Command/LoadDataFixturesDoctrineCommand.php

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
namespace Doctrine\Bundle\FixturesBundle\Command;
1616

1717
use Doctrine\Bundle\DoctrineBundle\Command\DoctrineCommand;
18+
use Doctrine\Bundle\FixturesBundle\Loader\SymfonyFixturesLoader;
1819
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
1920
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
2021
use Doctrine\DBAL\Sharding\PoolingShardConnection;
2122
use InvalidArgumentException;
22-
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader as DataFixturesLoader;
2323
use Symfony\Component\Console\Input\InputInterface;
2424
use Symfony\Component\Console\Input\InputOption;
2525
use Symfony\Component\Console\Output\OutputInterface;
@@ -33,24 +33,30 @@
3333
*/
3434
class LoadDataFixturesDoctrineCommand extends DoctrineCommand
3535
{
36+
private $fixturesLoader;
37+
38+
public function __construct(SymfonyFixturesLoader $fixturesLoader)
39+
{
40+
parent::__construct();
41+
42+
$this->fixturesLoader = $fixturesLoader;
43+
}
44+
3645
protected function configure()
3746
{
3847
$this
3948
->setName('doctrine:fixtures:load')
4049
->setDescription('Load data fixtures to your database.')
41-
->addOption('fixtures', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'The directory to load data fixtures from.')
4250
->addOption('append', null, InputOption::VALUE_NONE, 'Append the data fixtures instead of deleting all data from the database first.')
4351
->addOption('em', null, InputOption::VALUE_REQUIRED, 'The entity manager to use for this command.')
4452
->addOption('shard', null, InputOption::VALUE_REQUIRED, 'The shard connection to use for this command.')
4553
->addOption('purge-with-truncate', null, InputOption::VALUE_NONE, 'Purge data by using a database-level TRUNCATE statement')
4654
->setHelp(<<<EOT
47-
The <info>%command.name%</info> command loads data fixtures from your bundles:
55+
The <info>%command.name%</info> command loads data fixtures from your application:
4856
4957
<info>php %command.full_name%</info>
5058
51-
You can also optionally specify the path to fixtures with the <info>--fixtures</info> option:
52-
53-
<info>php %command.full_name% --fixtures=/path/to/fixtures1 --fixtures=/path/to/fixtures2</info>
59+
Fixtures are services that are tagged with doctrine.fixture.orm.
5460
5561
If you want to append the fixtures instead of flushing the database first you can use the <info>--append</info> option:
5662
@@ -84,30 +90,10 @@ protected function execute(InputInterface $input, OutputInterface $output)
8490
$em->getConnection()->connect($input->getOption('shard'));
8591
}
8692

87-
$dirOrFile = $input->getOption('fixtures');
88-
if ($dirOrFile) {
89-
$paths = is_array($dirOrFile) ? $dirOrFile : array($dirOrFile);
90-
} else {
91-
/** @var $kernel \Symfony\Component\HttpKernel\KernelInterface */
92-
$kernel = $this->getApplication()->getKernel();
93-
$paths = array($kernel->getRootDir().'/DataFixtures/ORM');
94-
foreach ($kernel->getBundles() as $bundle) {
95-
$paths[] = $bundle->getPath().'/DataFixtures/ORM';
96-
}
97-
}
98-
99-
$loader = new DataFixturesLoader($this->getContainer());
100-
foreach ($paths as $path) {
101-
if (is_dir($path)) {
102-
$loader->loadFromDirectory($path);
103-
} elseif (is_file($path)) {
104-
$loader->loadFromFile($path);
105-
}
106-
}
107-
$fixtures = $loader->getFixtures();
93+
$fixtures = $this->fixturesLoader->getFixtures();
10894
if (!$fixtures) {
10995
throw new InvalidArgumentException(
110-
sprintf('Could not find any fixtures to load in: %s', "\n\n- ".implode("\n- ", $paths))
96+
'Could not find any fixture services to load.'
11197
);
11298
}
11399
$purger = new ORMPurger($em);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Doctrine Fixtures Bundle
5+
*
6+
* The code was originally distributed inside the Symfony framework.
7+
*
8+
* (c) Fabien Potencier <[email protected]>
9+
* (c) Doctrine Project
10+
*
11+
* For the full copyright and license information, please view the LICENSE
12+
* file that was distributed with this source code.
13+
*/
14+
15+
namespace Doctrine\Bundle\FixturesBundle\DependencyInjection\CompilerPass;
16+
17+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
19+
use Symfony\Component\DependencyInjection\Reference;
20+
21+
/**
22+
* @author Ryan Weaver <[email protected]>
23+
*/
24+
final class FixturesCompilerPass implements CompilerPassInterface
25+
{
26+
const FIXTURE_TAG = 'doctrine.fixture.orm';
27+
28+
public function process(ContainerBuilder $container)
29+
{
30+
$definition = $container->getDefinition('doctrine.fixtures.loader');
31+
$taggedServices = $container->findTaggedServiceIds(self::FIXTURE_TAG);
32+
33+
foreach ($taggedServices as $serviceId => $tags) {
34+
$definition->addMethodCall('addFixture', [new Reference($serviceId)]);
35+
}
36+
}
37+
}

DependencyInjection/DoctrineFixturesExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
namespace Doctrine\Bundle\FixturesBundle\DependencyInjection;
1616

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

3133
$loader->load('services.xml');
34+
35+
$container->registerForAutoconfiguration(ORMFixtureInterface::class)
36+
->addTag(FixturesCompilerPass::FIXTURE_TAG);
3237
}
3338
}

DoctrineFixturesBundle.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
namespace Doctrine\Bundle\FixturesBundle;
1616

17+
use Doctrine\Bundle\FixturesBundle\DependencyInjection\CompilerPass\FixturesCompilerPass;
18+
use Symfony\Component\DependencyInjection\ContainerBuilder;
1719
use Symfony\Component\HttpKernel\Bundle\Bundle;
1820

1921
/**
@@ -24,4 +26,9 @@
2426
*/
2527
class DoctrineFixturesBundle extends Bundle
2628
{
29+
public function build(ContainerBuilder $container)
30+
{
31+
$container->addCompilerPass(new FixturesCompilerPass());
32+
}
33+
2734
}

EmptyFixture.php

Lines changed: 0 additions & 26 deletions
This file was deleted.

Fixture.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,6 @@
2020
*
2121
* @author Javier Eguiluz <[email protected]>
2222
*/
23-
abstract class Fixture extends AbstractFixture implements ContainerAwareInterface, DependentFixtureInterface
23+
abstract class Fixture extends AbstractFixture implements ORMFixtureInterface
2424
{
25-
use ContainerAwareTrait;
26-
27-
public function getDependencies()
28-
{
29-
// 'EmptyFixture' is a fixture class that loads no data. It's required
30-
// because Doctrine doesn't allow to return an empty array in this method
31-
// See https://github.com/doctrine/data-fixtures/pull/252
32-
return array(EmptyFixture::class);
33-
}
3425
}

Loader/SymfonyFixturesLoader.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Doctrine Fixtures Bundle.
5+
* For the full copyright and license information, please view the LICENSE
6+
* file that was distributed with this source code.
7+
*/
8+
9+
namespace Doctrine\Bundle\FixturesBundle\Loader;
10+
11+
use Doctrine\Bundle\FixturesBundle\DependencyInjection\CompilerPass\FixturesCompilerPass;
12+
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
13+
use Doctrine\Common\DataFixtures\FixtureInterface;
14+
use Doctrine\Common\DataFixtures\Loader;
15+
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
16+
17+
/**
18+
* @author Ryan Weaver <[email protected]>
19+
*/
20+
final class SymfonyFixturesLoader extends ContainerAwareLoader
21+
{
22+
public function addFixture(FixtureInterface $fixture)
23+
{
24+
// see https://github.com/doctrine/data-fixtures/pull/274
25+
// this is to give a clear error if you do not have this version
26+
if (!method_exists(Loader::class, 'createFixture')) {
27+
$this->checkForNonInstantiableFixtures($fixture);
28+
}
29+
30+
parent::addFixture($fixture);
31+
}
32+
33+
/**
34+
* Overridden to not allow new fixture classes to be instantiated.
35+
*/
36+
protected function createFixture($class)
37+
{
38+
try {
39+
/*
40+
* We don't actually need to create the fixture. We just
41+
* return the one that already exists.
42+
*/
43+
return $this->getFixture($class);
44+
} catch (\InvalidArgumentException $e) {
45+
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));
46+
}
47+
}
48+
49+
/**
50+
* For doctrine/data-fixtures 1.2 or lower, this detects an unsupported
51+
* feature with DependentFixtureInterface so that we can throw a
52+
* clear exception.
53+
*
54+
* @param FixtureInterface $fixture
55+
* @throws \Exception
56+
*/
57+
private function checkForNonInstantiableFixtures(FixtureInterface $fixture)
58+
{
59+
if (!$fixture instanceof DependentFixtureInterface) {
60+
return;
61+
}
62+
63+
foreach ($fixture->getDependencies() as $dependency) {
64+
if (!class_exists($dependency)) {
65+
continue;
66+
}
67+
68+
if (!method_exists($dependency, '__construct')) {
69+
continue;
70+
}
71+
72+
$reflMethod = new \ReflectionMethod($dependency, '__construct');
73+
foreach ($reflMethod->getParameters() as $param) {
74+
if (!$param->isOptional()) {
75+
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));
76+
}
77+
}
78+
}
79+
}
80+
}

ORMFixtureInterface.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Doctrine\Bundle\FixturesBundle;
4+
5+
use Doctrine\Common\DataFixtures\FixtureInterface;
6+
7+
/**
8+
* Marks your fixtures that are specifically for the ORM.
9+
*/
10+
interface ORMFixtureInterface extends FixtureInterface
11+
{
12+
}

Resources/config/services.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66

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

13+
<service id="doctrine.fixtures.loader" class="Doctrine\Bundle\FixturesBundle\Loader\SymfonyFixturesLoader" public="false">
14+
<argument type="service" id="service_container" />
15+
</service>
1216
</services>
1317
</container>

0 commit comments

Comments
 (0)