From ee5eb61c41572963299c41489b9acc159cbc246d Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Thu, 23 Nov 2017 22:52:43 -0500 Subject: [PATCH] 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. --- .travis.yml | 39 ++++ Command/LoadDataFixturesDoctrineCommand.php | 42 ++--- .../CompilerPass/FixturesCompilerPass.php | 37 ++++ .../DoctrineFixturesExtension.php | 5 + DoctrineFixturesBundle.php | 7 + EmptyFixture.php | 26 --- Fixture.php | 11 +- Loader/SymfonyFixturesLoader.php | 80 ++++++++ ORMFixtureInterface.php | 12 ++ Resources/config/services.xml | 4 + Resources/doc/index.rst | 130 ++++++------- ...ndentOnRequiredConstructorArgsFixtures.php | 22 +++ .../FooBundle/DataFixtures/OtherFixtures.php | 14 ++ .../RequiredConstructorArgsFixtures.php | 19 ++ .../DataFixtures/WithDependenciesFixtures.php | 22 +++ Tests/Fixtures/FooBundle/FooBundle.php | 10 + Tests/IntegrationTest.php | 172 ++++++++++++++++++ UPGRADE.md | 44 +++++ composer.json | 6 +- phpunit.xml.dist | 20 ++ 20 files changed, 582 insertions(+), 140 deletions(-) create mode 100644 .travis.yml create mode 100644 DependencyInjection/CompilerPass/FixturesCompilerPass.php delete mode 100644 EmptyFixture.php create mode 100644 Loader/SymfonyFixturesLoader.php create mode 100644 ORMFixtureInterface.php create mode 100644 Tests/Fixtures/FooBundle/DataFixtures/DependentOnRequiredConstructorArgsFixtures.php create mode 100644 Tests/Fixtures/FooBundle/DataFixtures/OtherFixtures.php create mode 100644 Tests/Fixtures/FooBundle/DataFixtures/RequiredConstructorArgsFixtures.php create mode 100644 Tests/Fixtures/FooBundle/DataFixtures/WithDependenciesFixtures.php create mode 100644 Tests/Fixtures/FooBundle/FooBundle.php create mode 100644 Tests/IntegrationTest.php create mode 100644 UPGRADE.md create mode 100644 phpunit.xml.dist diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4e5fe38c --- /dev/null +++ b/.travis.yml @@ -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 diff --git a/Command/LoadDataFixturesDoctrineCommand.php b/Command/LoadDataFixturesDoctrineCommand.php index 41f67d11..0337f4dd 100644 --- a/Command/LoadDataFixturesDoctrineCommand.php +++ b/Command/LoadDataFixturesDoctrineCommand.php @@ -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; @@ -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(<<%command.name% command loads data fixtures from your bundles: +The %command.name% command loads data fixtures from your application: php %command.full_name% -You can also optionally specify the path to fixtures with the --fixtures option: - - php %command.full_name% --fixtures=/path/to/fixtures1 --fixtures=/path/to/fixtures2 +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 --append option: @@ -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); diff --git a/DependencyInjection/CompilerPass/FixturesCompilerPass.php b/DependencyInjection/CompilerPass/FixturesCompilerPass.php new file mode 100644 index 00000000..be470876 --- /dev/null +++ b/DependencyInjection/CompilerPass/FixturesCompilerPass.php @@ -0,0 +1,37 @@ + + * (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 + */ +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)]); + } + } +} diff --git a/DependencyInjection/DoctrineFixturesExtension.php b/DependencyInjection/DoctrineFixturesExtension.php index 0bf2fdd2..dbc7326f 100644 --- a/DependencyInjection/DoctrineFixturesExtension.php +++ b/DependencyInjection/DoctrineFixturesExtension.php @@ -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; @@ -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); } } diff --git a/DoctrineFixturesBundle.php b/DoctrineFixturesBundle.php index 17b596fe..e70ef885 100644 --- a/DoctrineFixturesBundle.php +++ b/DoctrineFixturesBundle.php @@ -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; /** @@ -24,4 +26,9 @@ */ class DoctrineFixturesBundle extends Bundle { + public function build(ContainerBuilder $container) + { + $container->addCompilerPass(new FixturesCompilerPass()); + } + } diff --git a/EmptyFixture.php b/EmptyFixture.php deleted file mode 100644 index d45351a2..00000000 --- a/EmptyFixture.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @internal - */ -final class EmptyFixture implements FixtureInterface -{ - public function load(ObjectManager $manager) - { - } -} diff --git a/Fixture.php b/Fixture.php index d412639b..f3aced5f 100644 --- a/Fixture.php +++ b/Fixture.php @@ -20,15 +20,6 @@ * * @author Javier Eguiluz */ -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); - } } diff --git a/Loader/SymfonyFixturesLoader.php b/Loader/SymfonyFixturesLoader.php new file mode 100644 index 00000000..958444ae --- /dev/null +++ b/Loader/SymfonyFixturesLoader.php @@ -0,0 +1,80 @@ + + */ +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)); + } + } + } + } +} diff --git a/ORMFixtureInterface.php b/ORMFixtureInterface.php new file mode 100644 index 00000000..f1514f94 --- /dev/null +++ b/ORMFixtureInterface.php @@ -0,0 +1,12 @@ + + + + + diff --git a/Resources/doc/index.rst b/Resources/doc/index.rst index 6bf13e78..33f4f5f5 100644 --- a/Resources/doc/index.rst +++ b/Resources/doc/index.rst @@ -4,13 +4,10 @@ DoctrineFixturesBundle Fixtures are used to load a "fake" set of data into a database that can then be used for testing or to help give you some interesting data while you're developing your application. This bundle makes creating fixtures *easy*, and -supports the `ORM`_ (MySQL, PostgreSQL, SQLite, etc.) and `ODM`_ (MongoDB, etc.). +supports the `ORM`_ (MySQL, PostgreSQL, SQLite, etc.). -Setup and Configuration ------------------------ - -Step 1: Download the Bundle -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Installation +------------ Open a command console, enter your project directory and run the following command to download the latest stable version of this bundle: @@ -19,56 +16,37 @@ following command to download the latest stable version of this bundle: composer require --dev doctrine/doctrine-fixtures-bundle -This command assumes you have Composer installed globally, as explained -in the `installation chapter`_ of the Composer documentation. - -Step 2: Enable the Bundle -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Next, add the following line to ``app/AppKernel.php`` to enable the -bundle for the ``dev`` and ``test`` environments only: - -.. code-block:: php +If you're *not* using Symfony Flex (i.e. Symfony 3 and lower), you will +also need to enable the bundle in your ``AppKernel`` class: // app/AppKernel.php - // ... - - class AppKernel extends Kernel - { - public function registerBundles() - { - // ... - if (in_array($this->getEnvironment(), array('dev', 'test'), true)) { - // ... - $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(); - } - - return $bundles; - } + // ... + // registerBundles() + if (in_array($this->getEnvironment(), array('dev', 'test'), true)) { // ... + $bundles[] = new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(); } Writing Fixtures ---------------- Data fixtures are PHP classes where you create objects and persist them to the -database. By default, these classes live in ``src/AppBundle/DataFixtures/ORM/`` -(``src/AppBundle/DataFixtures/MongoDB/`` when using ODM). +database. Imagine that you want to add some ``Product`` objects to you database. No problem! Just create a fixtures class and start adding products! .. code-block:: php - // src/AppBundle/DataFixtures/ORM/Fixtures.php - namespace AppBundle\DataFixtures\ORM; + // src/DataFixtures/AppFixtures.php + namespace App\DataFixtures; - use AppBundle\Entity\Product; + use App\Entity\Product; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\Persistence\ObjectManager; - class Fixtures extends Fixture + class AppFixtures extends Fixture { public function load(ObjectManager $manager) { @@ -84,8 +62,6 @@ Just create a fixtures class and start adding products! } } -That's it! Inside ``load()``, create and persist as many objects as you want. - .. tip:: You can also create multiple fixtures classes. See :ref:`multiple-files`. @@ -100,39 +76,41 @@ Once your fixtures have been written, load them by executing this command: # when using the ORM $ php bin/console doctrine:fixtures:load - # when using the ODM - $ php bin/console doctrine:mongodb:fixtures:load - .. caution:: By default the ``load`` command **purges the database**, removing all data from every table. To append your fixtures' data add the ``--append`` option. -This command looks inside the ``DataFixtures/ORM/`` (or ``DataFixtures/MongoDB/``) -directory of each bundle and executes all the classes that implement the -``FixtureInterface`` (for example, those extending from ``Fixture``). +This command looks for all services tagged with ``doctrine.fixture.orm``. If you're +using the `default service configuration`_, any class that implements ``ORMFixtureInterface`` +(for example, those extending from ``Fixture``) will automatically be registered +with this tag. + +To see other options for the command, run: -These are the options that you can add to the command: +.. code-block:: terminal -* ``--fixtures=/path/to/fixture`` to make the command load only the fixtures - defined in that directory (which can be any directory, not only the standard - ``DataFixtures/ORM/`` directory). This option can be set repeatedly to load - fixtures from several directories; -* ``--append`` to make the command append data instead of deleting it before - loading the fixtures; -* ``--em=manager_name`` (``--dm=manager_name``) to define explicitly the entity - manager or document manager to use when loading the data. + $ php bin/console doctrine:fixtures:load --help -Using the Container in the Fixtures ------------------------------------ +Accessing Services from the Fixtures +------------------------------------ In some cases you may need to access your application's services inside a fixtures -class. No problem! The container is available via the ``$this->container`` property -on your fixture class: +class. No problem! Your fixtures class is a service, so you can use normal dependency +injection: .. code-block:: php - // src/AppBundle/DataFixtures/ORM/Fixtures.php + // src/DataFixtures/AppFixtures.php + use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; + + // ... + private $encoder; + + public function __construct(UserPasswordEncoderInterface $encoder) + { + $this->encoder = $encoder; + } // ... public function load(ObjectManager $manager) @@ -140,14 +118,17 @@ on your fixture class: $user = new User(); $user->setUsername('admin'); - $encoder = $this->container->get('security.password_encoder'); - $password = $encoder->encodePassword($user, 'pass_1234'); + $password = $this->encoder->encodePassword($user, 'pass_1234'); $user->setPassword($password); $manager->persist($user); $manager->flush(); } +You can also access the container via the ``$this->container`` property. +But remember that not *all* services (i.e. private services) can be accessed +directly via the container. + .. _multiple-files: Splitting Fixtures into Separate Files @@ -171,7 +152,7 @@ exact same object via its name: .. code-block:: php - // src/AppBundle/DataFixtures/ORM/UserFixtures.php + // src/DataFixtures/UserFixtures.php // ... class UserFixtures extends Fixture { @@ -186,7 +167,7 @@ exact same object via its name: } } - // src/AppBundle/DataFixtures/ORM/GroupFixtures.php + // src/DataFixtures/GroupFixtures.php // ... class GroupFixtures extends Fixture { @@ -212,14 +193,15 @@ Loading the Fixture Files in Order Instead of defining the exact order in which all fixture files must be loaded, Doctrine uses a smarter approach to ensure that some fixtures are loaded before -others. Just add the ``getDependencies()`` method to your fixtures class -and return an array of the fixture classes that must be loaded before -this one: +others. Implement the ``DependentFixtureInterface`` and add a new +``getDependencies()`` method to your fixtures class. This will return +an array of the fixture classes that must be loaded before this one: .. code-block:: php - // src/AppBundle/DataFixtures/ORM/UserFixtures.php - namespace AppBundle\DataFixtures\ORM; + // src/DataFixtures/UserFixtures.php + namespace App\DataFixtures; + // ... class UserFixtures extends Fixture { @@ -227,17 +209,15 @@ this one: { // ... } - - // No need to define getDependencies() here because this fixture - // doesn't need any other fixture loaded before } - // src/AppBundle/DataFixtures/ORM/GroupFixtures.php - namespace AppBundle\DataFixtures\ORM; + // src/DataFixtures/GroupFixtures.php + namespace App\DataFixtures; // ... - use AppBundle\DataFixtures\ORM\UserFixtures; + use App\DataFixtures\UserFixtures; + use Doctrine\Common\DataFixtures\DependentFixtureInterface; - class GroupFixtures extends Fixture + class GroupFixtures extends Fixture implements DependentFixtureInterface; { public function load(ObjectManager $manager) { @@ -253,5 +233,5 @@ this one: } .. _`ORM`: http://symfony.com/doc/current/doctrine.html -.. _`ODM`: http://symfony.com/doc/current/bundles/DoctrineMongoDBBundle/index.html .. _`installation chapter`: https://getcomposer.org/doc/00-intro.md +.. _`default service configuration`: https://symfony.com/doc/current/service_container.html#service-container-services-load-example diff --git a/Tests/Fixtures/FooBundle/DataFixtures/DependentOnRequiredConstructorArgsFixtures.php b/Tests/Fixtures/FooBundle/DataFixtures/DependentOnRequiredConstructorArgsFixtures.php new file mode 100644 index 00000000..78fc7315 --- /dev/null +++ b/Tests/Fixtures/FooBundle/DataFixtures/DependentOnRequiredConstructorArgsFixtures.php @@ -0,0 +1,22 @@ +addServices(function(ContainerBuilder $c) { + $c->autowire(OtherFixtures::class) + ->addTag(FixturesCompilerPass::FIXTURE_TAG); + + $c->autowire(WithDependenciesFixtures::class) + ->addTag(FixturesCompilerPass::FIXTURE_TAG); + + $c->setAlias('test.doctrine.fixtures.loader', new Alias('doctrine.fixtures.loader', true)); + }); + $kernel->boot(); + $container = $kernel->getContainer(); + + /** @var ContainerAwareLoader $loader */ + $loader = $container->get('test.doctrine.fixtures.loader'); + + $actualFixtures = $loader->getFixtures(); + $this->assertCount(2, $actualFixtures); + $actualFixtureClasses = array_map(function($fixture) { + return get_class($fixture); + }, $actualFixtures); + + $this->assertSame([ + OtherFixtures::class, + WithDependenciesFixtures::class, + ], $actualFixtureClasses); + $this->assertInstanceOf(WithDependenciesFixtures::class, $actualFixtures[1]); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The getDependencies() method returned a class (Doctrine\Bundle\FixturesBundle\Tests\Fixtures\FooBundle\DataFixtures\RequiredConstructorArgsFixtures) that has required constructor arguments. Upgrade to "doctrine/data-fixtures" version 1.3 or higher to support this. + */ + public function testExceptionWithDependenciesWithRequiredArguments() + { + // see https://github.com/doctrine/data-fixtures/pull/274 + // When that is merged, this test will only run when using + // an older version of that library. + if (method_exists(Loader::class, 'createFixture')) { + $this->markTestSkipped(); + } + + $kernel = new IntegrationTestKernel('dev', true); + $kernel->addServices(function(ContainerBuilder $c) { + $c->autowire(DependentOnRequiredConstructorArgsFixtures::class) + ->addTag(FixturesCompilerPass::FIXTURE_TAG); + + $c->autowire(RequiredConstructorArgsFixtures::class) + ->setArgument(0, 'foo') + ->addTag(FixturesCompilerPass::FIXTURE_TAG); + + $c->setAlias('test.doctrine.fixtures.loader', new Alias('doctrine.fixtures.loader', true)); + }); + $kernel->boot(); + $container = $kernel->getContainer(); + + /** @var ContainerAwareLoader $loader */ + $loader = $container->get('test.doctrine.fixtures.loader'); + + $loader->getFixtures(); + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage The "Doctrine\Bundle\FixturesBundle\Tests\Fixtures\FooBundle\DataFixtures\RequiredConstructorArgsFixtures" fixture class is trying to be loaded, but is not available. Make sure this class is defined as a service and tagged with "doctrine.fixture.orm". + */ + public function testExceptionIfDependentFixtureNotWired() + { + // only runs on newer versions of doctrine/data-fixtures + if (!method_exists(Loader::class, 'createFixture')) { + $this->markTestSkipped(); + } + + $kernel = new IntegrationTestKernel('dev', true); + $kernel->addServices(function(ContainerBuilder $c) { + $c->autowire(DependentOnRequiredConstructorArgsFixtures::class) + ->addTag(FixturesCompilerPass::FIXTURE_TAG); + + $c->setAlias('test.doctrine.fixtures.loader', new Alias('doctrine.fixtures.loader', true)); + }); + $kernel->boot(); + $container = $kernel->getContainer(); + + /** @var ContainerAwareLoader $loader */ + $loader = $container->get('test.doctrine.fixtures.loader'); + + $loader->getFixtures(); + } +} + +class IntegrationTestKernel extends Kernel +{ + use MicroKernelTrait; + + private $servicesCallback; + + private $randomKey; + + public function __construct($environment, $debug) + { + $this->randomKey = rand(100, 999); + + parent::__construct($environment, $debug); + } + + public function getName() + { + return parent::getName().$this->randomKey; + } + + public function registerBundles() + { + return [ + new FrameworkBundle(), + new DoctrineFixturesBundle(), + new FooBundle(), + ]; + } + + public function addServices($callback) + { + $this->servicesCallback = $callback; + } + + protected function configureRoutes(RouteCollectionBuilder $routes) + { + } + + protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader) + { + $c->setParameter('kernel.secret', 'foo'); + $callback = $this->servicesCallback; + $callback($c); + } + + public function getCacheDir() + { + return sys_get_temp_dir().'/doctrine_fixtures_bundle'.$this->randomKey; + } + + public function getLogDir() + { + return sys_get_temp_dir(); + } +} \ No newline at end of file diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..22520224 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,44 @@ +UPGRADE to 3.0 +============== + +* The automatic loading of fixtures in a directory (e.g. + AppBundle\DataFixtures\ORM) was removed. Instead, register + your fixture classes as services and tag then with doctrine.fixture.orm. + This will happen automatically if you're using the Symfony 3.3 + or higher default service configuration and your fixture classes + extend the normal ``Doctrine\Bundle\FixturesBundle\Fixture`` class, + or implement the new ``Doctrine\Bundle\FixturesBundle\ORMFixtureInterface``. + +* The base ``Fixture`` class no longer implements ``ContainerAwareInterface`` + and so no longer have a ``$this->contanier`` property. You *can* manually + implement this interface. Or, a better idea is to update your fixtures + to use dependency injection: + +```diff +class MyFixture extends Fixture +{ ++ private $someService; + ++ public function __construct(SomeService $someService) ++ { ++ $this->someService = $someService; ++ } + + public function load(ObjectManager $manager) + { +- $this->container->get('some_service')->someMethod(); ++ $this->someService->someMethod(); + } +} +``` + +* The base ``Fixture`` class no longer implements ``DependentFixtureInterface``. + If you want to have a ``getDependencies()`` method, be sure to imlement + this interface explicitly: + +```diff ++ use Doctrine\Common\DataFixtures\DependentFixtureInterface; + +- class MyFixture extends Fixture ++ class MyFixture extends Fixture implements DependentFixtureInterface +``` diff --git a/composer.json b/composer.json index f8cca437..1960c49a 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ } ], "require": { - "php": ">=5.3.2", + "php": ">=5.5.9|^7.0", + "symfony/framework-bundle": "^3.3|^4.0", "symfony/doctrine-bridge": "~2.7|~3.0|~4.0", "doctrine/doctrine-bundle": "~1.0", "doctrine/data-fixtures": "~1.0" @@ -32,5 +33,8 @@ "branch-alias": { "dev-master": "2.4.x-dev" } + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.3" } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000..96387152 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,20 @@ + + + + + + ./Tests + + + + + + . + + ./Resources + ./Tests + ./vendor + + + +