Создание сложных моделей с использованием гидраторов: агрегат / стратегии
Ранее я создавал сложные модели (объект, содержащий много других типов моделей), используя сервисный слой с различными преобразователями, передаваемыми в качестве конструктора.
например.
class UserService{
public function __construct(UserMapper $userMapper, AddressMapper $addressMapper, AppointmentsMapper $appointmentsMapper){}
public function loadById($id) : User {
$user = $this->userMapper->find($id);
$appointments = $this->appointmentsMapper->findByUser($user);
$user->setAppointments($appointments);
$address = $this->addressMapper->findByUser($user);
$user->setAddress($address);
//..etc..
}
}
Выше приведен упрощенный пример. В моем домене я использую несколько сервисов, используемых на фабриках для создания графа сложных объектов.
Прочитав очень интересную статью о MaltBlue о гидраторах агрегатов, я попытался применить этот подход, чтобы упростить процесс создания объектов. Мне нравится идея создания HydratingResulset с RowObjectPrototype, установленным для объекта, который будет возвращен.
Я думаю, что мне нужно несколько советов о том, как сделать эту работу в реальном мире. Например, при использовании AggregateHydrator я могу загрузить историю встреч пользователей на основе идентификатора пользователя, переданного в гидратор.
class UserModelHydratorFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator) {
$serviceManager = $serviceLocator->getServiceLocator();
/**
* Core hydration
*/
$arrayHydrator = new ArraySerializable();
$arrayHydrator->addStrategy('dateRegistered', new DateTimeStrategy());
$aggregateHydrator = new AggregateHydrator();
$aggregateHydrator->add($arrayHydrator);
$aggregateHydrator->add($serviceLocator->get('Hydrator\Address'));
$aggregateHydrator->add($serviceLocator->get('Hydrator\Appointments'));
return $aggregateHydrator;
}
}
... например, с гидратором пользовательских адресов, который выглядит следующим образом:
class UserAddressHydrator implements HydratorInterface{
protected $locationMapper;
public function __construct(LocationMapper $locationMapper){
$this->locationMapper = $locationMapper;
}
public function hydrate(array $data, $object){
if(!$object instanceof User){
return;
}
if(array_key_exists('userId', $data)){
$object->setAddress($this->locationMapper->findByClientId($data['userId']));
}
return $object;
}
}
Это работает отлично. Несмотря на использование подхода AggregateHydrator, это означает, что для каждого объекта, имеющего в качестве свойства Address, требуется собственный гидратор. Таким образом, другой (почти идентичный) гидратор понадобился бы, если бы я строил модель компании, у которой также был адрес, поскольку вышеупомянутый гидратор жестко запрограммирован для заполнения модели пользователя (и ожидает данные, содержащие ключ userId). Это означает, что для каждого отношения / взаимодействия (has-a) потребуется собственный гидратор, чтобы генерировать это. Это нормально? Поэтому мне понадобится UserAddressHydrator, CompanyAddressHydrator, SupplierAddressHydrator, AppointmentAddressHydrator - все почти идентично приведенному выше коду, просто заполняя другой объект?
Было бы намного чище иметь один AddressHydrator, который принимает addressId и возвращает модель Address. Это заставило меня взглянуть на стратегии гидратора, которые, кажется, идеальны, хотя они работают только с одним значением, поэтому не могут, например, просмотреть входящий массив, чтобы увидеть, существует ли ключ идентификации pk/fk / и загрузить на основе этого,
Я был бы признателен за разъяснение этого подхода, мне кажется, что я заблудился на этом пути.
1 ответ
Вы абсолютно правы. Стратегии Hydrator работают только с одним значением, которое соответствует члену вашей организации. Таким образом, вы должны добавить несколько стратегий для вашего гидратора. С другой стороны, вы можете наследовать от \Zend\Hydrator\AbstractHydrator
и перезаписать addStrategy()
метод для обработки массивов с более чем одним именем. С этим решением вы можете установить тот же Hydrator на значения массива, к которому вы добавляете addStrategy()
,
Простая Гидраторская Стратегия
В этом примере показано использование простой стратегии гидратора.
class UserHydratorFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $oServiceLocator)
{
$oHydrator = (new ClassMethods(false))
->addStrategy('address', new AddressHydratorStrategy())
->addStrategy('company', new AddressHydratorStrategy());
return $oHydrator;
}
}
Это пример фабрики гидраторов для объекта пользователя. На этом заводе нормальный ClassMethods
гидратор называется. Этот тип гидратора предполагает использование методов получения и установки в вашей сущности для гидрирования членов сущности. Кроме того, добавлены стратегии для членов адреса и компании.
class AddressHydratorStrategy extends DefaultStrategy
{
public function hydrate($aData)
{
return (new ClassMethods(false))
->hydrate($aData, new AdressEntity())
}
}
Стратегия просто позволяет добавлять виды дочерних объектов. Если в ваших данных есть адрес или ключ компании, к ним будет добавлен объект адреса.
class UserEntity
{
protected $name;
protected $address;
protected $company;
public function getName() : string
{
return $this->name;
}
public function setName(string $sName) : UserEntity
{
$this-> name = $sName;
return $this;
}
public function getAddress() : AddressEntity
{
return $this->address;
}
public function setAddress(AddressEntity $oAddress) : UserEntity
{
$this->address = $oAddress;
return $this;
}
public function getCompany() : AddressEntity
{
return $this->company;
}
public function setCompany(AddressEntity $oCompany) : UserEntity
{
$this->company = $oCompany;
return $this;
}
}
Вы заметили подсказки типа? Например, setAddress
метод занимает AddressEntity
объект как параметр. Этот объект будет сгенерирован стратегией, которую мы добавили к ClassMethods
увлажняющий.
Далее следует вызов гидратора с некоторыми данными, которые приведут к нестестовому комплексу. UserEntity
объект.
$oUserHydrator = $this->getServiceLocator(UserHydrator::class);
$oUserHydrator->hydrate(
[
'name' => 'Marcel',
'address' =>
[
'street' => 'bla',
'zipcode' => 'blubb',
],
'company' =>
[
'street' => 'yadda',
'zipcode' => 'yadda 2',
],
],
new UserEntity()
);
Использование в результирующих наборах
Чтобы прояснить ситуацию, вот небольшой пример того, как я использую гидраторы и стратегии непосредственно в наборах результатов.
class UserTableGateway extends TableGateway
{
public function __construct(Adapter $oAdapter, $oUserHydrator)
{
$oPrototype = new HydratingResultSet(
$oHydrator,
new UserEntity()
);
parent::__construct('user_table', $oAdapter, null, $oPrototype);
}
public function fetchUser()
{
// here a complex join sql query is fired
// the resultset is of the hydrated prototype
}
}
В этом примере TableGateway
класс инициализируется с прототипом, который является HydratingResultSet
, Он использует гидратор от UserHydratorFactory
, Стратегии Hydrator имеют смысл, когда сложные данные напрямую выбираются из базы данных или другого источника, такого как веб-сервис, который возвращает вложенные данные.
Заключение
Для меня лично работа с гидраторными стратегиями имеет больше смысла, чем работа с агрегатным гидратором. Конечно, вы можете добавить в стратегию не более одного имени. В противном случае вы можете, как я сказал в начале, перезаписать addStrategy
Метод в унаследованном классе в соответствии с вашими требованиями. Стратегии гидратора в моих глазах менее кодирующие.