Мне интересно узнать о лучших методах загрузки сложных объектов. Для начала, я собираюсь обрисовать в общих чертах некоторые шаблоны, прежде чем перейти к проблеме. Предположим следующее: Простая модель предметной области. Клиент загружается с использованием 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 для возврата полностью сформированных объектов Client из 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 со всеми полностью сформированными связанными объектами. Но у меня есть некоторые ресурсы, где весь граф объектов не нужен - например, при просмотре таблицы всех клиентов - нет необходимости загружать дополнительную информацию.
Поэтому я думал о способах использования фабрик, чтобы выбирать, какие зависимости включать.
Решение 1 Фабрика для каждого варианта использования. Если нужны только данные клиента (без зависимостей), то создайте серию фабрик, т.е. ClientFactory, SimpleClientFactory, ComplexClientFactory, ClientWithAppointmentsFactory и т. д. Кажется избыточным и не очень пригодным для повторного использования.
Решение 2. Используйте параметр options, определенный в 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, которая передает их фабрике гидратора:
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;
}
}
Это кажется чистым решением, так как зависимости могут быть определены для каждого запроса. Однако я не думаю, что это использует параметр options по назначению - в документации указано, что этот параметр предназначен для передачи параметров конструктора объекту, а не для определения того, какую логику фабрика должна использовать для загрузки зависимостей.
Любые советы или альтернативные решения для достижения вышеизложенного были бы замечательными. Спасибо за чтение.