I had previously built complex models (an object containing many other types of model) using a service layer with different mappers passed in as the constructor.
eg.
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..
}
}
The above is a simplified example. In my domain i am using multiple services used in factories to create a complex object graph.
After reading the very interesting article on MaltBlue about aggregate hydrators, i've tried to adopt this approach to simplify the object creation process. I love the idea of creating a HydratingResulset with the RowObjectPrototype set to the object to be returned.
I think i need some pointers on how to make this work in the real world. For instance, when using an AggregateHydrator, i can load a users Appointment history based on the user id passed into the hydrator.
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;
}
}
...with, for instance, the User Address hydrator looking like this:
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;
}
}
This works perfectly. Although using the AggregateHydrator approach, it means that every object that has an Address as a property means it will needs it's own hydrator. So another (near-identical) hydrator will be needed if i were building a Company model, that also had an Address, since the above Hydrator is hard-coded to populate a User model (and expects the data containing a userId key). This means for each relationship / interaction (has-a) will need it's own hydrator to generate this. Is that normal? So i would need a UserAddressHydrator, CompanyAddressHydrator, SupplierAddressHydrator, AppointmentAddressHydrator - all near-identical to the above code, just populating a different object?
It would be much cleaner to have a single AddressHydrator, that takes an addressId and returns the Address model. This led me to look at hydrator strategies, which seems to be perfect, although they only work on a single value so cannot, for instance, look through the incoming array to see if a pk/fk / identifying key exists and load based on that.
I'd appreciate some clarification on this approach, it feels like i've got lost along the way.