Как зарегистрировать пользовательский гидратор в Zend Framework 2?


В приложении Apigility driven ZF2 я хочу использовать пользовательский 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('PortfolioV2RestImageService'));
                    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',
        ),
        ...
    ),
),
...

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

После некоторой отладки я пришел к этому месту в коде ZFHalMetadataMetadata#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
    }
}

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

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


EDIT:

Я перемещаю заводской код из 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('PortfolioV2RestImageService'));
                return $projectHydrator;
            }
        ),
    );
}

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

module.config.php

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

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

ZendServiceManagerExceptionServiceNotFoundException

Файл: в /var/www в/мой проект/поставщика/на базе ZendFramework/на базе ZendFramework/библиотека/Зенд/элемент ServiceManager/элемент ServiceManager.php: 550 Сообщение: ZendStdlibHydratorHydratorPluginManager:: получить не удалось извлечение или создание экземпляра для PortfolioV2RestImageService

2 2

2 ответа:

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

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

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

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()