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;
}
}
我想知道加载复杂对象的最佳做法。 首先,在解决问题之前,我将概述一些样板文件。 假设如下:一个简单的域模型客户端使用 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;
}
}