BehatExtension / DoctrineDataFixturesExtension

Doctrine Data Fixtures Extension for Behat
MIT License
20 stars 7 forks source link

Injecting dependencies seems not to work #22

Closed tristanbes closed 5 years ago

tristanbes commented 6 years ago
Q A
Bug report? yes
Feature request? no
BC Break report? no
RFC? / Specification -
Project version 5.0.1

Hi,

I'm migrating from a Symfony 3 application to 4.1. It seems that dependency injection is not done. Is this a bug or am I missing something ?

The error is:

Too few arguments to function Context\Fixtures\FixturesLoader::__construct(), 0 passed in /srv/app/vendor/doctrine/data-fixtures/lib/Doctrine/Common/DataFixtures/Loader.php on line 193 and exactly 2 expected

When i'm running: bin/behat -c apps/behat.yml -p front --tags="front_intro_on_page"

// behat.yml

default:
    autoload: '%paths.base%/features/Context'
    extensions:
        Behat\Symfony2Extension:
            kernel:
                env: test
                debug: true
                path: admin/AdminKernel.php
                class: AdminKernel
                bootstrap: vendor/autoload.php
        BehatExtension\DoctrineDataFixturesExtension\Extension:
            lifetime:   feature
            autoload:   false
            use_backup: true
            directories:
                - apps/features/Context/Fixtures
            fixtures: ~
        Behatch\Extension: ~
    suites:
        default:
            contexts:
                - Context\FeatureContext
                - behatch:context:browser
    translation:
        locale: en
<?php

namespace Context\Fixtures;

...

class FixturesLoader extends Fixture
{
    private $loader;
    private $doctrine;

    public function __construct(NativeLoader $loader, ManagerRegistry $doctrine)
    {
        $this->loader   = $loader;
        $this->doctrine = $doctrine;
    }

    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $objectSet = $this->loader->loadFiles(
            [
                __DIR__.'/companies.yml',
                __DIR__.'/medias.yml',

When I run : bin/console debug:container FixturesLoader --env=test it gives me

Information for Service "Context\Fixtures\FixturesLoader"
=========================================================

 ---------------- ---------------------------------
  Option           Value
 ---------------- ---------------------------------
  Service ID       Context\Fixtures\FixturesLoader
  Class            Context\Fixtures\FixturesLoader
  Tags             doctrine.fixture.orm
  Public           yes
  Synthetic        no
  Lazy             no
  Shared           yes
  Abstract         no
  Autowired        yes
  Autoconfigured   yes
 ---------------- ---------------------------------

But when I run bin/behat -c apps/behat.yml -p front --tags="front_intro_on_page"

The errors appears

Spomky commented 6 years ago

I will try to reproduce that issue. Stay tuned.

tristanbes commented 6 years ago

Hi, any news by chance? :-)

Spomky commented 6 years ago

Just to be sure, BehatExtension\DoctrineDataFixturesExtension\Bundle\BehatDoctrineDataFixturesExtensionBundle must be added in your bundle list. Is it correctly listed?

tristanbes commented 6 years ago

So, that was indeed a good remark. The bundle wasn't listed in our Kernel.php :s Adding it doesn't fix the issue though (this->isDebug() returns true).

if ($this->isDebug()) {
    $bundles[] = new Symfony\Bundle\DebugBundle\DebugBundle();
    $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
    $bundles[] = new BehatExtension\DoctrineDataFixturesExtension\Bundle\BehatDoctrineDataFixturesExtensionBundle;
}
Spomky commented 6 years ago

OK thanks. It looks like there is a real trouble here. This is a little bit old school, but what if the fixture implements ContainerAwareInterface? (not sure this interface is still available)

namespace Context\Fixtures;

...

class FixturesLoader extends Fixture implements ContainerAwareInterface
{
     /**
     * @var ContainerInterface
     */
    private $container;

    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $loader = $this->container->get(NativeLoader::class);
        $doctrine = $this->container->get(ManagerRegistry::class);
        $objectSet = $loader->loadFiles(
            [
                __DIR__.'/companies.yml',
                __DIR__.'/medias.yml',
tristanbes commented 6 years ago
In FixturesLoader.php line 49:
  Call to a member function get() on null

L49 = $loader = $this->container->get(NativeLoader::class);

class FixturesLoader extends Fixture implements ContainerAwareInterface
{
    private $container;
    private $doctrine;

    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $loader = $this->container->get(NativeLoader::class);
        $doctrine = $this->container->get(ManagerRegistry::class);

        $objectSet = $loader->loadFiles(
            [
tristanbes commented 6 years ago

Also, doing so breaks the raw command: php bin/console doctrine:fixtures:load --env=test

  The "Doctrine\Common\Persistence\ManagerRegistry" service or alias has been removed or inlined when the container was compiled. You should either make i
  t public, or stop using the container directly and use dependency injection instead.
danaki commented 6 years ago

Can confirm, autowire of constructor arguments is broken.

takeit commented 5 years ago

OK thanks. It looks like there is a real trouble here. This is a little bit old school, but what if the fixture implements ContainerAwareInterface? (not sure this interface is still available)

namespace Context\Fixtures;

...

class FixturesLoader extends Fixture implements ContainerAwareInterface
{
     /**
     * @var ContainerInterface
     */
    private $container;

    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }

    /**
     * {@inheritdoc}
     */
    public function load(ObjectManager $manager)
    {
        $loader = $this->container->get(NativeLoader::class);
        $doctrine = $this->container->get(ManagerRegistry::class);
        $objectSet = $loader->loadFiles(
            [
                __DIR__.'/companies.yml',
                __DIR__.'/medias.yml',

I tried it like that and it seems to work for the first time I run Behat. When I run Behat for the second time it does not autowire services, I need to clear the cache. When I do this it works again. Maybe this will give you a hint on how to fix it.

Spomky commented 5 years ago

Hi all,

Sorry for the late answer. @takeit it only works when the fixture loader is declared as a service, but not when loaded from an arbitrary folder (i.e. with the directories option). You can automatically clear your cache using the adamquaile/behat-command-runner-extensio extension.

I am working on a fix, but I have to create my own loader that will wrap the current one and the Symfony one (with container/DI features).

Spomky commented 5 years ago

Hi,

I am still looking for a way to make it possible for fixtures loaded by the extension, but at the moment, the only solution to get rid of this issue is to declare the fixtures as services. I created a new branch as POC. There are 3 fixture loaders in the folder DemoBundle/Tests/DataFixtures:

There are all declared as services and, thanks to the autowiring/autoconfiguration from SF, there is nothing complicated.

Can you give it a try and let me know if it works for you? This issue will remain opened until a more convenient solution is found.

takeit commented 5 years ago

Hi @Spomky, thanks a lot, I will give it a try!

For my use case I managed to fix it like that for now: https://github.com/BehatExtension/DoctrineDataFixturesExtension/compare/v5.0...takeit:container_aware?expand=1

Spomky commented 5 years ago

Hi @takeit,

I see in your fork that you select either the SF Loader, the Container Aware loader or the default loader. I removed this part as it is not needed anymore: it is useless when fixture loaders are correctly tagged.

The issue still remains regardless which loader is used. Unknown fixtures are either instantiated without any constructor argument or an exception is thrown depending on the loader.

From what I understand, it is not possible to instantiate new classes with DI as the container is already initialized when the extension is called. At this point, private services are not reachable anymore and it is not possible to add new service definition correctly.

I will modify the loader to allow Container-Aware fixtures to be loaded through directories and fixtures options.

By the way, it appears to me that the autoload option is useless. The fixtures from the bundles should be declared as services directly by the bundles. I think I will depreciate this option and remove it for v6.x.

Wrap-up:

Spomky commented 5 years ago

As mentioned earlier, there is no convenient way to inject the dependencies to fixtures loaders added through the directories and fixtures options.

To solve that issue you have to choices:

I now close this issue as it is solved from my point of view. Feel free to open it again if you still have troubles with it or contact me to discuss on a private channel (English/French).

The v6.0 I've just released should also help you to move forward.