ZF FactoryInterface - 使用选项参数配置加载依赖项

ZF FactoryInterface - using options parameter for configuring loading dependencies

我想知道加载复杂对象的最佳做法。 首先,在解决问题之前,我将概述一些样板文件。 假设如下:一个简单的域模型客户端使用 table 网关加载,每个阶段都使用工厂来注入依赖项:

namespace My\Model\Client;
class Client implements InputFilterProviderInterface
{
    /**@var integer*/
    protected $id;
    /**@var InputFilter*/
    protected $inputFilter;
    /**@var Preferences */
    protected $preferences;
    /**@var Orders*/
    protected $orders;
    /**@var Contacts*/
    protected $contacts;      
}

此客户端对象的工厂:

namespace My\Model\Client;
class ClientFactory implements FactoryInterface
{
    public function($container, $requestedName, $options)
    {
        $client = new Client();
        $client->setInputFilter($container->get('InputFilterManager')->get('ClientInputFilter'));
        return $client;
    }
}

接下来是使用 TableGateway 的映射器工厂:

namespace My\Model\Client\Mapper;
class ClientMapperFactory implements FactoryInterface
{
     public function __invoke($container, $requestedName, $options)
     {
        return new ClientMapper($container->get(ClientTableGateway::class));
     }
}

TableGatewayFactory:

namespace My\Model\Client\TableGateway
class ClientTableGatewayFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $hydrator = new ArraySerialisable();
        $rowObjectPrototype = $container->get(Client::class);
        $resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
        $tableGateway = new  TableGateway('clients', $container->get(Adapter::class), null, $resultSet);
        return $tableGateway;

注意使用 HydratingResultset return 来自 ResultSet 的完整客户端对象。 这一切都很好。 现在 Client 对象有几个相关的对象作为属性,所以在使用 HydratingResultSet 的同时,我将添加一个 AggregateHydrator 来加载它们:

class ClientTableGatewayFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        **$hydrator = $container->get('HydratorManager')->get(ClientHydrator::class);**
        $rowObjectPrototype = $container->get(Client::class);
        $resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
        $tableGateway = new  TableGateway('clients', $container->get(Adapter::class), null, $resultSet);
        return $tableGateway;
    }

最后,客户水瓶厂:

class ClientHydratorFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        //base ArraySerializable for Client object hydration
        $arrayHydrator = new ArraySerializable();
        $arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());

        $aggregateHydrator = new AggregateHydrator();
        $aggregateHydrator->add($arrayHydrator);

        $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsAddressHydrator::class));
        $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsOrdersHydrator::class));
        $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsPreferencesHydrator::class));
        $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsContactsHydrator::class));
        return $aggregateHydrator;
    }
}

...上述保湿器的要点如下:

class ClientsAddressHydrator implements HydratorInterface
{
    /** @var AddressMapper */
    protected $addressMapper;

    public function __construct(AddressMapper $addressMapper){
        $this->addressMapper = $addressMapper;
    }

    public function extract($object){return $object;}

    public function hydrate(array $data, $object)
    {
        if(!$object instanceof Client){
            return;
        }

        if(array_key_exists('id', $data)){
            $address = $this->addressMapper->findClientAddress($data['id']);
            if($address instanceof Address){
                $object->setAddress($address);
            }
        }
        return $object;
    }
}

终于到了正题。上面的工作非常完美,并且会非常干净地加载一个 Client 对象,所有相关对象都已完全形成。但是我有一些不需要整个对象图的资源 - 例如,当查看所有客户端的 table 时 - 不需要加载任何更多信息。

所以我一直在考虑使用工厂来选择要包含哪些依赖项的方法。

解决方案 1 每个用例的工厂。如果只需要 Client 数据(没有依赖),那么创建一系列工厂即 ClientFactory、SimpleClientFactory、ComplexClientFactory、ClientWithAppointmentsFactory 等。看起来多余且不太可重用。

方案二 使用 FactoryInterface 中定义的 options 参数将 "loading" 选项传递给 hydrator 工厂,例如:

  class ViewClientDetailsControllerFactory implements FactoryInterface
    {
         //all Client info needed - full object graph
         public function __invoke($container, $requestedName, $options)
         {
            $controller = new ViewClientDetailsController();
            $loadDependencies = [
                'loadPreferences' => true,
                'loadOrders' => true,
                'loadContacts' => true
             ];
            $clientMapper = $container->get(ClientMapper::class, '', $loadDependencies);
            return $controller;
         }
    }



   class ViewAllClientsControllerFactory implements FactoryInterface
    {
         //Only need Client data - no related objects
         public function __invoke($container, $requestedName, $options)
         {
            $controller = new ViewAllClientsController();
            $loadDependencies = [
                'loadPreferences' => false,
                'loadOrders' => false,
                'loadContacts' => false
             ];
            $clientMapper = $container->get(ClientMapper::class, '', $loadDependencies);
            return $controller;
         }
    }

映射器工厂将选项传递给 table网关工厂,网关工厂将它们传递给水化器工厂:

class ClientTableGatewayFactory implements FactoryInterface
{
     public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $hydrator = $container->get('HydratorManager')->get(ClientHydrator::class, '', $options);
        $rowObjectPrototype = $container->get(Client::class);
        $resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
        $tableGateway = new  TableGateway('clients', $container->get(Adapter::class), null, $resultSet);
        return $tableGateway;
}

最后,我们可以在这里定义加载到客户端的信息量:

class ClientHydratorFactory implements FactoryInterface
    {
        public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
        {
            //base ArraySerializable for Client object hydration
            $arrayHydrator = new ArraySerializable();
            $arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());

            $aggregateHydrator = new AggregateHydrator();
            $aggregateHydrator->add($arrayHydrator);
            if($options['loadAddress'] === true){
                   $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsAddressHydrator::class));            
            }
            if($options['loadOrders'] === true){
                $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsOrdersHydrator::class));
            }
            if($options['loadPreferences'] === true){
                $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsPreferencesHydrator::class));
            }
            if($options['loadContacts'] === true){
                $aggregateHydrator->add($container->get('HydratorManager')->get(ClientsContactsHydrator::class));
            }
            return $aggregateHydrator;
        }
    }

这似乎是一个干净的解决方案,因为可以根据请求定义依赖项。但是我不认为这是按预期使用选项参数 - 文档指出该参数应该用于将构造函数参数传递给对象,而不是定义工厂应该使用什么逻辑来加载依赖项。

任何实现上述目标的建议或替代解决方案都会很棒。感谢阅读。

创建一个包含所有可能组合的大调色板不仅是一场噩梦,而且是宣告的自杀。

使用选项

我也不建议你选择这个选项。我的意思是,它并没有那么糟糕,但它有一个主要问题:每次你实例化你的水化器时,你应该记住传递这些选项,否则你会得到一个 "empty hydrator"。同样的逻辑适用于所有使用这些水化器的东西。

既然你真的想移除你不需要的保湿器,我建议避免这个解决方案,因为这样你总是被迫 来声明你需要哪种保湿器(而且,老实说,我总是会忘记这样做...... ^^)。 如果您添加一个新的水龙头,您将必须检查您的项目并添加新的选项。真的不值得努力...

这就是我向您推荐下一个解决方案的原因

移除不必要的保湿器

在 99% 的情况下,绘图员使用水化器。因此,我认为拥有一个映射器会更干净,默认情况下,returns 总是相同类型的数据(->单个水合器),但可以对其进行修改以删除一组特定的水合器。

AggregateHydrator 内部,所有水化器都转换为监听器并附加到 EventManager。我在尝试获取所有事件时遇到了一些问题,因此我打开了创建一个聚合水化器的选项,该水化器可以分离一个水化器:

class DetachableAggregateHydrator extends AggregateHydrator 
{

    /**
     * List of all hydrators (as listeners)
     *
     * @var array
     */
    private $listeners = [];

    /**
     * {@inherit}
     */
    public function add(HydratorInterface $hydrator, int $priority = self::DEFAULT_PRIORITY): void 
    {
        $listener = new HydratorListener($hydrator);
        $listener->attach($this->getEventManager(), $priority);

        $this->listeners[get_class($hydrator)] = $listener;

    }

    /**
     * Remove a single hydrator and detach its listener
     * 
     * @param string $hydratorClass
     */
    public function detach($hydratorClass) 
    {
        $listener = $this->listeners[$hydratorClass];
        $listener->detach($this->getEventManager());
        unset($listener);
        unset($this->listeners[$hydratorClass]);

    }

}

然后,在 TableGatewayFactory 中:

class ClientTableGatewayFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $hydrator = $container->get('HydratorManager')->get(ClientHydrator::class);
        $rowObjectPrototype = $container->get(Client::class);
        $resultSet = new HydratingResultSet($hydrator, $rowObjectPrototype);
        $adapter = $container->get(Adapter::class);
        $tableGateway = new  TableGateway('clients', $adapter, null, $resultSet);
        return $tableGateway;
    }

}

ClientHydratorFactory

class ClientHydratorFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $aggregateHydrator = new DetachableAggregateHydrator();

        $arrayHydrator = new ArraySerializable();
        $arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());
        $aggregateHydrator->add($arrayHydrator);

        $hydratorManager = $container->get('HydratorManager');
        $aggregateHydrator->add($hydratorManager->get(ClientsAddressHydrator::class));
        $aggregateHydrator->add($hydratorManager->get(ClientsOrdersHydrator::class));
        $aggregateHydrator->add($hydratorManager->get(ClientsPreferencesHydrator::class));
        $aggregateHydrator->add($hydratorManager->get(ClientsContactsHydrator::class));

        return $aggregateHydrator;
    }
}

您只需要让 tablegateway 可以在映射器之外访问:

class ClientMapper 
{

    private $tableGateway;

    // ..
    // Other methods
    // ..

    public function getTableGateway(): TableGateway 
    {
        return $this->tableGateway;
    }
}

现在您可以选择不想连接的保湿器。

假设您有两个控制器:

  • ClientInfoController,您需要客户及其地址、偏好和联系方式的地方
  • ClientOrdersController,您需要客户的订单

他们的工厂将是:

class ClientInfoController implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $clientMapper = $container->get(ClientMapper::class);

        // Orders are unnecessary
        $resultSetPrototype = $clientMapper->getTableGateway()->getResultSetPrototype();
        $resultSetPrototype->getHydrator()->detach(ClientsOrdersHydrator::class);

        return $aggregateHydrator;
    }
}

class ClientOrdersController implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $clientMapper = $container->get(ClientMapper::class);

        // Orders are unnecessary
        $resultSetPrototype = $clientMapper->getTableGateway()->getResultSetPrototype();
        $resultSetPrototype->getHydrator()->detach(ClientsAddressHydrator::class);
        $resultSetPrototype->getHydrator()->detach(ClientsPreferencesHydrator::class);
        $resultSetPrototype->getHydrator()->detach(ClientsContactsHydrator::class);

        return $aggregateHydrator;
    }
}