Aggregate Hydrators for Sophisticated Object Hydration

"Use an AggregateHydrator for Sophisticated Object Hydration"

Often times object hydration isn’t a simple matter, and requires a more nuanced, more sophisticated solution than the out-of-the-box options, available in Zend Framework, provide. Today we showcase one such solution - the Aggregate Hydrator.


Introduced in Zend Framework 2, and consequently available in Zend Framework 3, three base hydrators are available for populating an object from a set of data, as well as for extracting data from a populated object. These are:

  • ObjectProperty: Any data key matching a publicly accessible property will be hydrated; any public properties will be used for extraction
  • ClassMethods: Any data key matching a setter method will be called in order to hydrate; any method matching a getter method will be called for extraction
  • ArraySerializable: Objects must implement either the exchangeArray() or populate() methods to support hydration, and the getArrayCopy() method to support extraction

Each of these do a good job of hydrating a simple object, in an increasing level of sophistication and flexibility, from ObjectProperty through ArraySerializable. However, what if the object you’re hydrating is itself composed of several objects, built via something like the composite pattern for example?

Let’s say, for example, you were creating a shopping cart system and were building a user object to model a user in the shopping process. Such a user object could be expected to have the following core details:

  • username
  • password
  • first name
  • last name
  • email address
  • physical addresses

Focusing in on the addresses for a moment, a shopping user usually has two, one for their home, or shipping, address, and one their billing address. These can contain the same information, or be different, but are required to ensure that the goods can be paid for and delivered.

The Data Model

So how would we model and hydrate such a semi-sophisticated entity? If you’re not concerned about database normalization, then you might have a one-to-one mapping between the object and the table in which the information is stored (assuming you’re using a database as your data store).

But if you’re doing even a bit of normalization, you’re likely going to have at least a user table and an address table. Then, you might have a join table which mapped a user to their addresses, potentially also storing an address type as well so as to easily differentiate between the multiple addresses stored for the user.

The Object Model

That’s the data storage side simplistically taken care of. What about the object space? What would the objects look like? How would they be composed? To keep it easy to model and manipulate from a programmatic perspective, you could construct the objects as in the UML class diagram below. There you can see that a user object can have one or more address objects.

"User/Address ER Diagram"

Taking this approach, we can maintain some database normalization, and also create objects which has a good respect for separation of concerns. However, whilst we’ve achieved a good data and class design, it’s not going to be too straight-forward to hydrate these objects.

So the question then is, how do we make object construction and hydration easy as well? Good question. To answer that, I suggest that one of the following approaches. Firstly, you could create a query on the user table, performing a right join on the two address tables, so that if the address wasn’t set, you’d still have user data.

The SQL query would look something like this:

SELECT u.id, u.first_name, u.last_name, u.email,
  ha.street_number as home_street_number,
  ha.street_name as home_street_name,
  sa.street_number as shipping_street_number,
  sa.street_name as shipping_street_name
FROM user u
RIGHT JOIN address ha ON (ha.id = u.home_address_id)
RIGHT JOIN address sa ON (sa.id = u.shipping_address_id)

Note: You’d have to alias the address columns appropriately so that in the aggregate result, it’s clear as to where each columns came from.

Alternatively, you could query on the user table, joining it on the link table to get the respective address ids. This would retrieve the core user details. With that information available, you could then invoke a method on an address TableGateway to retrieve the shipping and billing addresses, providing the user id to filter on.

If you took that approach, the underlying queries might look something more like this, where the second query is using a named placeholder:

-- Get the user data
SELECT u.id, u.first_name, u.last_name, u.email
FROM user u;

-- Get the address data
SELECT a.street_number, a.street_name
FROM address a
LEFT JOIN user_address ua ON (a.id = ua.address_id)
WHERE u.user_id = :userId

Let’s assume that the latter approach is the one you chose to take. How do you make the process as simple, maintainable, and testable as possible? Well, besides being a lofty set of goals, is it even achievable?

Until recently, I wasn’t sure it was. I’d been building everything separately, the long way, using the base three hydrators in a rather convoluted manner. That is, until the need pushed me to find a better solution.

The AggregateHydrator

The solution I came across was the AggregateHydrator. If you’ve not heard of it, quoting directly from the manual:

You typically want to use an aggregate hydrator when you want to hydrate or extract data from complex objects that implement multiple interfaces, and therefore need multiple hydrators to handle that in subsequent steps.

This description neatly describes the situation I found myself in. When I retrieved a user via its TableGateway, using a method such as findById etc, I wanted the returned object to be fully hydrated with all the associated sub-objects already hydrated as well.

But, at the same time I didn’t want to pollute the concerns of the user TableGateway with knowledge or functionality of address details which it should be concerned of. So here’s how I approached it.

  1. Create two TableGateway classes, one for the user and one for the address
  2. Create a hydrator for the user and address objects
  3. Create a hydrator factory, which handles instantiation of the various hydrators and executes the aggregate hydration process
  4. Combine it all in the configuration of a TableGatewayAbstractFactory

Now you might be thinking that that’s a lot to cover in one post, and it is. So I hope you’re ready. Oh, before we go on too much further, I think it’s essential to explain why this is an approach worth pursuing.

You might be wondering why anyone would go to all this effort in the first place. Here’s why:

  • Once done, the upfront investment of time and effort pays for itself in spades later
  • It’s testable
  • It’s easy to document
  • It’s easy to maintain and extend
  • Assuming you know your way around the ZF2 ServiceManager, it’s very transparent

Stick with me and you’ll see why soon.

The User Entity

The User entity is a simple entity object which has protected properties for all, or most, of the properties which you might expect to be available when working with a user. It also has an addresses property which is an SplObjectStorage object, which will contain two AddressEntity objects. Setter and getter methods are available for each property.

Note: The reason for using an SplObjectStorage object is that it makes it easy to manage a list of objects using PHP’s native classes, instead of having to code yet another class to handle it.

<?php

namespace App\Entity;

class User
{
    protected $id;
    protected $firstName;
    protected $lastName;
    protected $email;
    /** @var \SplObjectStorage $addresses */
    protected $addresses;

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getAddresses()
    {
        if (is_null($this->addresses)) {
            $this->addresses = new addresses();
        }
        return $this->addresses;
    }

    public function setAddresses($addresses)
    {
        $this->addresses = $addresses;
    }

    public function setFirstName($firstName)
    {
        $this->firstName = $firstName;
    }

    public function setLastName($lastName)
    {
        $this->lastName = $lastName;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }

    // ...remaining methods
}

The Address Entity

As with the user entity, the address entity is a simple PHP class which has protected properties for the properties which it will store. It also has setters and getters for manipulating each one.

<?php

namespace App\Entity;

class Address
{
    protected $streetNumber;
    protected $streetName;
    protected $city;
    protected $state;
    protected $postCode;
    protected $country;

    public function getId()
    {
        return $this->id;
    }

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getStreetNumber()
    {
        return $this->streetNumber;
    }

    public function getStreetName()
    {
        return $this->streetName;
    }

    public function getCity()
    {
        return $this->city;
    }

    public function getState()
    {
        return $this->state;
    }

    public function getPostCode()
    {
        return $this->postCode;
    }

    public function getCountry()
    {
        return $this->country;
    }

    // ...remaining methods
}

The User Hydrator

Now for the user hydrator. This class extends Zend Framework’s AbstractHydrator, which requires that the hydrate method be implemented, which you can find the definition of in Zend\Hydrator\HydrationInterface.

The method is given an array, called $data, which is the data to hydrate the object with, and an object, $object, which is the object to hydrate. I’ve implemented the method, first checking if the object supplied is an instance of User. If it is, I’ve used the object’s setter methods to hydrate it with the data provided.

<?php

namespace App\Hydrator;

use Zend\Hydrator\AbstractHydrator;
use App\Entity\User;

class UserEntityHydrator extends AbstractHydrator
{
    public function hydrate(array $data, $object)
    {
        if (!$object instanceof User) {
            return $object;
        }

        if ($this->propertyAvailable('id', $data)) {
            $object->setId($data['id']);
        }

        if ($this->propertyAvailable('firstName', $data)) {
            $object->setFirstName($data['firstName']);
        }

        if ($this->propertyAvailable('lastName', $data)) {
            $object->setLastName($data['lastName']);
        }

        return $object;
    }

    protected function propertyAvailable($property, $data)
    {
        return (array_key_exists($property, $data)
          && !empty($data[$property]));
    }
}

The Address Hydrator

The UserAddressHydrator is a little different, but similar. This one has a constructor dependency of an AddressTable TableGateway object, which we’ll see later. During hydration, as with the User object, we first check if the object is a User entity. If it is, we call the TableGateway object’s fetchByUserId method, passing in the user id provided in $data.

<?php

namespace App\Hydrator;

use App\Entity\User;
use App\TableGateway\AddressTable;
use Zend\Hydrator\HydratorInterface;

class UserAddressEntityHydrator implements HydratorInterface
{
    protected $tableGateway;

    public function __construct(AddressTable $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    public function hydrate(array $data, $object)
    {
        if (!$object instanceof User) {
            return $object;
        }

        if ($this->propertyAvailable('id', $data)) {
            $object->setAddresses(
                $this->tableGateway->fetchByUserId($data['id'])
            );
        }

        return $object;
    }

    protected function propertyAvailable($property, $data)
    {
        return (array_key_exists($property, $data)
          && !empty($data[$property]));
    }
}

The User Hydrator Factory

Now for one last hydrator, the UserHydratorFactory. This is responsible for instantiating an aggregate hydrator which uses the UserEntity and UserAddressEntity hydrators to fully hydrate a UserEntity.

It’s a callable by definition of the fact that it implements the __invoke magic method. This, just for the sakes of simplicity, calls a protected method prepareHydrator, passing to it the AddressTable TableGateway object which it was provided.

The prepareHydrator method then instantiates a new AggregateHydrator object, adding on to it a UserEntityHydrator and a UserAddressEntityHydrator. The result of the method is to return the AggregateHydrator object.

<?php

namespace App\Hydrator;

use App\Hydrator;
use App\TableGateway\AddressTable;
use Zend\Hydrator\Aggregate\AggregateHydrator;

class UserHydratorFactory
{
    protected $hydrator;

    public function __invoke(AddressTable $address)
    {
        return $this->prepareHydrator($address);
    }

    protected function prepareHydrator(AddressTable $address)
    {
        $this->hydrator = new AggregateHydrator();
        $userHydrator = new Hydrator\UserEntityHydrator();

        $this->hydrator->add($userHydrator);
        $this->hydrator->add(
            new Hydrator\UserAddressEntityHydrator($address)
        );

        return $this->hydrator;
    }
}

Dependency Configuration

Now that we have all of the classes created, we need to ensure that they’re registered with the ServiceLocator. If you’re using Zend Expressive then in config/autoload/dependencies.global.php, ensure you have a reference in the invokables array, as I have below.

<?php

return [
    'dependencies' => [
        'invokables' => [
            App\Hydrator\UserHydratorFactory::class
              => App\Hydrator\UserHydratorFactory::class,
        ]
    ]
];

The code for this article was developed using Zend Expressive. But if you’re working with Zend Framework 2, then add the line above in config/autoload/global.php similar to what you see below.

<?php

return [
    'service_manager' => [
        'invokables' => [
            App\Hydrator\UserHydratorFactory::class
              => App\Hydrator\UserHydratorFactory::class,
        ]
    ],
];

 ServiceManager Configuration

I’ve covered how to do ServiceManager configuration in a previous post. So if you’re not familiar with the process, definitely check out that post, as you’ll need it to follow along with this section.

That being said, we first retrieve a UserHydratorFactory service from the ServiceLocator, passing an AddressTable service to it when calling its __invoke method. The rest of the instantiation is the same as I’ve covered previously. A new HydratingResultSet object is initialized, passing in the AggregateHydrator object and a rowObjectPrototype, which is a UserEntity. This is then passed to the initialization of the UserTableGateway object as the third parameter.

case ('App\TableGateway\UserTableGateway'):
    $hydratorFactory = $serviceLocator->get(
      'App\Hydrator\UserHydratorFactory'
    );
    $hydrator = $hydratorFactory(
      $serviceLocator->get('App\TableGateway\AddressTable')
    );
    $rowObjectPrototype = new UserEntity();
    $resultSet = new HydratingResultSet(
        $hydrator,
        $rowObjectPrototype
    );
    $tableGateway = new TableGateway(
        'tbluser', $dbAdapter, $resultSet
    );
    break;

Now, when we retrieve data from tbluser the objects returned will be transparently hydrated with any linked addresses for us. We will never need to do it manually anywhere else in our codebase.

Over To You

And that’s how to use an AggregateHydrator to perform sophisticated, even complex hydration of objects in your Zend Framework applications. Sure, there’s a bit to think about, and a number of moving parts. However - when done correctly, hydration is all done for us, in one central, testable, configurable location. All of the objects are testable with proper separation of concerns.

Have you used AggregateHydrators before? What’s been your experience? Could you see yourself using them?

CC Image (background of main image) Courtesy of Becky Lai on Flickr



About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.

Want To Be A Zend Framework Guru?

Drop your email in the box below, and get awesome tutorialsjust like this one — straight to your inbox, PLUS exclusive content only available by email.