1

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

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;      
}

此 Client 对象的工厂:

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 从 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;
    }
}

最后,我们解决了这个问题。以上工作完美,将非常干净地加载一个客户端对象,所有相关对象都完全形成。但是我有一些资源不需要整个对象图 - 例如,当查看所有客户端的表时 - 不需要加载任何更多信息。

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

解决方案 1 每个用例的工厂。如果只需要客户端数据(没有依赖关系),则创建一系列工厂,即 ClientFactory、SimpleClientFactory、ComplexClientFactory、ClientWithAppointmentsFactory 等。似乎是多余的,并且不是很可重用。

解决方案 2 使用 FactoryInterface 中定义的选项参数将“加载”选项传递给水合器工厂,例如:

  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;
         }
    }

映射器工厂将选项传递给 tablegateway 工厂,后者将它们传递给 hydrator 工厂:

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;
        }
    }

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

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

4

1 回答 1

1

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

使用选项

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

由于您实际上想要删除不需要的保湿剂,我建议您避免使用这种解决方案,因为这样您总是被迫声明您需要哪些保湿剂(老实说,我总是会忘记这样做.. ^^)。如果您添加新的保湿剂,您将必须完成您的项目并添加新选项。真的不值得努力...

这就是为什么我建议你下一个解决方案

去除不必要的保湿剂

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

在内部AggregateHydrator,所有 hydrator 都转换为 listeners 并附加到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;
    }
}
于 2019-11-20T14:22:50.240 回答