Как зарегистрировать собственный гидратор в Zend Framework 2?

В приложении ZF2, управляемом Apigility, я хочу использовать пользовательский Hydrator,

Module учебный класс

class Module implements ApigilityProviderInterface {
    ...
    public function getServiceConfig() {
        return array(
            'factories' => array(
                ...
                'MyNamespace\\Hydrator\\ProjectHydrator' => function(ServiceManager $serviceManager) {
                    $projectHydrator = new ProjectHydrator();
                    $projectHydrator->setImageService($serviceManager->get('Portfolio\V2\Rest\ImageService'));
                    return $projectHydrator;
                }
            ),
            ...
        );
    }
}

module.config.php

...
'zf-hal' => array(
    'metadata_map' => array(
        ...
        'Portfolio\\V2\\Rest\\Project\\ProjectEntity' => array(
            'entity_identifier_name' => 'id',
            'route_name' => 'portfolio.rest.project',
            'route_identifier_name' => 'id',
            // 'hydrator' => 'Zend\\Stdlib\\Hydrator\\ClassMethods',
            'hydrator' => 'MyNamespace\\Hydrator\\ProjectHydrator',
        ),
        ...
    ),
),
...

Это игнорируется, когда коллекция извлекается, но это другая проблема (см. Здесь). Когда требуется одна сущность, запускается механизм гидратина, но он не использует мою фабрику для создания экземпляра.

После некоторой отладки я пришел в это место в ZF\Hal\Metadata\Metadata#setHydrator(...) код:

if (is_string($hydrator)) {
    if (null !== $this->hydrators
        && $this->hydrators->has($hydrator)
    ) {
        $hydrator = $this->hydrators->get($hydrator);
    } elseif (class_exists($hydrator)) {
        $hydrator = new $hydrator(); // <-- here
    }
}

Обычай Hydrator создается напрямую. (В моем случае это приводит к фатальной ошибке, так как он пытается затем выполнить метод на ProjectHydrator#imageService, это не установлено). Я посмотрел на Metadata#hydrators (типа Zend\Stdlib\Hydrator\HydratorPluginManager) и нашел только четыре по умолчанию invocables, вот почему null !== $this->hydrators && $this->hydrators->has($hydrator) является false и прямой экземпляр судят.

Итак, я думаю, я должен зарегистрировать свой гидратор. Где / как я могу это сделать?


РЕДАКТИРОВАТЬ:

Я перемещаю заводской код из Module#getServiceConfig() в Module#getHydratorConfig():

public function getHydratorConfig() {
    return array(
        'factories' => array(
            // V2
            'MyNamespace\\Hydrator\\ProjectHydrator' => function(ServiceManager $serviceManager) {
                $projectHydrator = new ProjectHydrator();
                $projectHydrator->setImageService($serviceManager->get('Portfolio\V2\Rest\ImageService'));
                return $projectHydrator;
            }
        ),
    );
}

То же самое над массивом конфигурации в module.config.php (нужен Factory учебный класс):

module.config.php

return array(
    ...
    'hydrators' => array(
        'factories' => array(
            'MyNamespace\\Hydrator\\ProjectHydrator' => 'MyNamespace\\Hydrator\\ProjectHydratorFactory',
        ), 
    ),
);

Но это заканчивается ошибкой:

Zend \ ServiceManager \ Exception \ ServiceNotFoundException

Файл: /var/www/my-project/vendor/zendframework/zendframework/library/Zend/ServiceManager/ServiceManager.php:550 Сообщение: Zend\Stdlib\Hydrator\HydratorPluginManager::get не удалось получить или создать экземпляр для портфолио \V2\ Отдых \ImageService

2 ответа

Решение

Что вы сделали в своем редактировании, используя getHydratorConfig было правильно, сообщение об ошибке, которое вы видите, вызвано тем, что в вашем заводском методе вы пытаетесь получить службу изображений из диспетчера подключаемых модулей гидратора.

Решение простое, так как с другими менеджерами плагинов вам нужно позвонить getServiceLocator() на экземпляре диспетчера гидраторов, чтобы получить основной локатор сервиса (он же менеджер сервиса)

Итак, небольшое изменение в вашем заводском методе должно решить проблему...

public function getHydratorConfig() {
    return array(
        'factories' => array(
            // V2
            'MyNamespace\\Hydrator\\ProjectHydrator' => function(ServiceManager $serviceManager) {
                $projectHydrator = new ProjectHydrator();
                // get the image service from the main service locator
                $imageService = $serviceManager->getServiceLocator()->get('Portfolio\V2\Rest\ImageService');
                $projectHydrator->setImageService($imageService);
                return $projectHydrator;
            }
        ),
    );
}

Как это работает: экземпляр передается в замыкание в качестве аргумента Zend\Stdlib\Hydrator\HydratorPluginManager, HydratorPluginManager наследует от Zend\ServiceManager\AbstractPluginManagerдитя Zend\ServiceManager\ServiceManager, Но метод ServiceManager#get(...) логически переопределяется в HydratorPluginManager и предоставляет только гидраторы. Тем не менее, родительский класс реализует Zend\ServiceManager\ServiceLocatorAwareInterfaceтак что мы доступ к его внутреннему ServiceLocator и над ServiceLocator на весь набор доступных услуг.

Кроме того, я обычно предпочитаю называть переменную serviceLocator в методе фабрики ($serviceManager в вашем коде), чтобы он отражал фактический менеджер плагинов в использовании, так для фабрики гидратора, $hydratorsдля фабрики форм, $formElemements, и так далее. Я резервирую использование $services обращаться только к главному сервис-менеджеру. Я считаю, что это полезное напоминание о том, что getServiceLocator() Вызов необходим для любых зависимостей, не расположенных в этом конкретном менеджере.

Быстрый ответ. замещать HydratorManager с заказным.

В вашем глобальном конфиге:

return array(
  'service_manager' => array(
    'factories' => (
        'HydratorManager' => 'MyNamespace\Hydrator\HydratorManagerFactory',
    ),
  ),
);

MyNamespace \ Hydrator \ HydratorManagerFactory.php:

<?php

namespace MyNamespace\Hydrator;

use Zend\Mvc\Service\HydratorManagerFactory as BaseHydratorManagerFactory;

class HydratorManagerFactory extends BaseHydratorManagerFactory
{
    const PLUGIN_MANAGER_CLASS = 'MyNamespace\Hydrator\HydratorPluginManager';
}

MyNamespace \ Hydrator \ HydratorPluginManager.php

<?php

namespace MyNamespace\Hydrator;

use Zend\Stdlib\Hydrator\HydratorPluginManager as BaseHydratorPluginManager;

class HydratorPluginManager extends BaseHydratorPluginManager
{
    protected $factories = array(
        'mynamespacehydratorprojecthydrator' => 'MyNamespace\\Hydrator\\ProjectHydratorFactory',
    );
}

Обратите внимание, что фабрика получит экземпляр Zend\ServiceManager\AbstractPluginManager, Вы можете получить общий ServiceLocator, вызывающий метод $services->getServiceLocator()

Другие вопросы по тегам