Внедрение зависимостей в фикстуры

Класс является фикстурой, если он реализует интерфейс \Doctrine\Fixture\Fixture. В интерфейсе определены два метода: import и purge. При использовании фикстур в реальных условиях зачастую возникает потребность использовать различные компоненты из приложения. Например, в фикстурах может потребоваться ObjectManager Doctrine2 или сервис для загрузки данных из XML-формата. Далее будет описываться способ внедрения зависимостей в фикстуры.

Внедрение зависимостей через FixtureInitializer

Быстрый старт

  • Создать Aware-интерфейс. Если фикстура реализует данный интерфейс, то значит, что в нее требуется внедрить зависимость:
    • Aware-интерфейс должен определять setter'ы, позволяющие передать зависимость.

interface DependencyServiceAwareInterface
{
    function setDependencyService(DependencyService $dependencyService);
}

  • Создать FixtureInitializer:
    • Создать класс, реализующий интерфейс Nnx\DoctrineFixtureModule\FixtureInitializer\FixtureInitializerInterface;
    • В качестве аргументов конструктора указать сервисы, которые необходимо внедрить в фикстуру;
    • В классе реализовать метод getSubscribedEvents, возвращающий массив с двумя значениями: "import" и "purge";
    • Реализовать в классе два метода: import и purge;
    • В методах import и purge проверяется, что если фикстура реализует заданный Aware-интерфейс, то в нее устанавливается требуемая зависимость.

use Doctrine\Fixture\Event\ImportFixtureEventListener;
use Doctrine\Fixture\Event\PurgeFixtureEventListener;
use Nnx\DoctrineFixtureModule\FixtureInitializer\FixtureInitializerInterface;

class MyFixtureInitializer implements
    FixtureInitializerInterface,
    ImportFixtureEventListener,
    PurgeFixtureEventListener
{
    /**
     * Constructor.
     *
     * @param DependencyService $dependencyService
     */
    public function __construct(DependencyService $dependencyService)
    {
        $this->dependencyService = $dependencyService;
    }

    /**
     * {@inheritdoc}
     */
    public function getSubscribedEvents()
    {
        return array(
            ImportFixtureEventListener::IMPORT,
            PurgeFixtureEventListener::PURGE,
        );
    }

    /**
     * {@inheritdoc}
     */
    public function purge(FixtureEvent $event)
    {
        if ( ! ($fixture instanceof MyServiceAwareInterface)) {
            return;
        }

        $fixture->setDependencyService($this->dependencyService);
    }

    /**
     * {@inheritdoc}
     */
    public function import(FixtureEvent $event)
    {
        if ( ! ($fixture instanceof MyServiceAwareInterface)) {
            return;
        }

        $fixture->setDependencyService($this->dependencyService);
    }
}
  • Создать фабрику для FixtureInitializer:
    • Создать стандартную фабрику, которая реализует интерфейс \Zend\ServiceManager\FactoryInterface;
    • Из ServiceLocator получить необходимые зависимости. Необходимо помнить что ServiceLocator в фабриках, используемых для менеджеров плагинов, является самим менеджером плагинов;
    • Создать FixtureInitializer и передать в конструктор необходимые зависимости.

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class MyFixtureInitializerFactory implements FactoryInterface
{
    /**
     * @inheritDoc
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $appServiceLocator = $serviceLocator instanceof AbstractPluginManager ? $serviceLocator->getServiceLocator() : $serviceLocator;

        /** @var DependencyService $dependencyService */
        $dependencyService = $appServiceLocator->get(DependencyService::class);

        return new MyFixtureInitializer($dependencyService);
    }
}


  • Зарегестрировать FixtureInitializer в менеджере плагинов \Nnx\DoctrineFixtureModule\Executor\FixtureExecutorManagerInterface:
    • В секцию ['nnx_fixture_initializer']['factories'] конфигурационных файлов приложения, добавить созданный FixtureInitializer

return [
    'nnx_fixture_initializer' => [
        'factories'          => [
            MyFixtureInitializer::class => MyFixtureInitializerFactory::class,
        ]
    ],
];

  • Добавить FixtureInitializer в список тех FixtureInitializer, которые запускаются для фикстур
    • Добавить в секцию ['nnx_doctrine_fixture_module']['fixtureInitializer'] имя FixtureInitializer

return [
    'nnx_doctrine_fixture_module' => [
        'fixtureInitializer' => [
            MyFixtureInitializer::class
        ]
    ],
];

Описание FixtureInitializer

Для внедрения зависимостей используется модификация паттерна "Method Injection", а именно – "Interface Injection". Непосредственно после создания фикстуры запускаются FixtureInitializer.

FixtureInitializer – это класс, реализующий \Nnx\DoctrineFixtureModule\FixtureInitializer\FixtureInitializerInterface. Интерфейс \Nnx\DoctrineFixtureModule\FixtureInitializer\FixtureInitializerInterface расширяет \Doctrine\Common\EventSubscriber.

Таким образом, в классе FixtureInitializer необходимо реализовать метод getSubscribedEvents, в котором нужно вернуть массив с именами событий, на которые подписываемся.

Для реализации FixtureInitializer необходимо подписаться на два события:

  • import - событие, бросаемое при запуске процесса импорта;
  • purge - событие, бросаемое, при запуске процесса удаления данных.

Пример FixtureInitializer:


use Doctrine\Fixture\Event\ImportFixtureEventListener;
use Doctrine\Fixture\Event\PurgeFixtureEventListener;
use Nnx\DoctrineFixtureModule\FixtureInitializer\FixtureInitializerInterface;

class MyFixtureInitializer implements
    FixtureInitializerInterface,
    ImportFixtureEventListener,
    PurgeFixtureEventListener
{
    /**
     * Constructor.
     *
     * @param DependencyService $dependencyService
     */
    public function __construct(DependencyService $dependencyService)
    {
        $this->dependencyService = $dependencyService;
    }

    /**
     * {@inheritdoc}
     */
    public function getSubscribedEvents()
    {
        return array(
            ImportFixtureEventListener::IMPORT,
            PurgeFixtureEventListener::PURGE,
        );
    }

    /**
     * {@inheritdoc}
     */
    public function purge(FixtureEvent $event)
    {
        if ( ! ($fixture instanceof MyServiceAwareInterface)) {
            return;
        }

        $fixture->setDependencyService($this->dependencyService);
    }

    /**
     * {@inheritdoc}
     */
    public function import(FixtureEvent $event)
    {
        if ( ! ($fixture instanceof MyServiceAwareInterface)) {
            return;
        }

        $fixture->setDependencyService($this->dependencyService);
    }
}


В фикстуре необходимо реализовать два метода: import и purge. В методах обычно происходит проверка того, что фикстура реализует заданный интерфейс. В случае если фикстура интерфейс реализует, то происходит внедрение соответствующей зависимости в фикстуру.

Регистрация FixtureInitializer

Для работы с FixtureInitializer используется специальный менеджер плагинов \Nnx\DoctrineFixtureModule\FixtureInitializer\FixtureInitializerManagerInterface. Для регистрации FixtureInitializer в менеджере плагинов необходимо добавить соответствующий плагин в секцию nnx_fixture_initializer.


return [
    'nnx_fixture_initializer' => [
        'invokables'         => [

        ],
        'factories'          => [
            MyFixtureInitializer::class => MyFixtureInitializerFactory::class,
        ],
        'abstract_factories' => [

        ],
        'shared'             => [

        ]
    ],
];

FixtureInitializer бывают двух типов:

  • Initializer для контекста. Такие FixtureInitializer создаются и подписываются на события import и purge. В качестве аргумента передаются данные, переданные в аргументах import и purge объекта имплементирующего, \Nnx\DoctrineFixtureModule\Executor\ExecutorInterface
  • Статические Initializer. Такие FixtureInitializer не создаются каждый раз заново, также они подписываются единожды на события import и purge всех FixtureInitializer

Добавление Initializer для контекста

Для добавления Initializer для контекста необходимо в секции ['nnx_doctrine_fixture_module']['contextInitializer'] добавить имя Initializera'a (по этому имени он будет получен из \Nnx\DoctrineFixtureModule\Executor\FixtureExecutorManagerInterface)

Добавление статического Initializer

Для добавления Initializer для контекста необходимо в секции ['nnx_doctrine_fixture_module']['fixtureInitializer'] добавить имя Initializera'a (по этому имени он будет получен из \Nnx\DoctrineFixtureModule\Executor\FixtureExecutorManagerInterface)