Zend Framework 2 – Hydrators, Models and the TableGateway Pattern

Zend Framework 2

Synopsis

Zend Framework 2 comes packed with an assortment of new features and functionality and greatly streamlines how common scenarios, such as interacting with datasources and application configuration and caching are implemented.

Whether it’s the new HTML5 form elements and view helpers, the new implementation of Zend\Http, the Service Manager, the Event Manager or that Modules are now first-class citizens – you can’t help but see that it’s bursting at the seams.

Update: Thanks to Jeff for pointing out the missing method in TableEntityMapper, which caused a fatal error. This has now been added.

But one set of features has really been helping me of late ones that really have me smiling; these are: Hydrators, Models and Table Gateways. If you’re new to ZF2 or database interaction with frameworks, then you’re in a perfect position as today’s post will be giving you a good introduction to the basics of using both together.

We’ll be working through sample code which will show you how to create models, which are decoupled from data source logic, yet via a simple ServiceManager configuration and a not too complex hydrator, will be able to extract information from a database query and auto-fill the model with it – ready to be used.

Why This Approach?

In the past, in Zend Framework 1, when you wanted to have a datasource agnostic model, it wasn’t always so simple to implement. Though there is still a lot of talk of PHP & MySQL; we all know that there is a veritable cornucopia of choice, including MongoDB, CouchDB, PostgreSQL, Cassandra, Redis and much, much, more.

Applications we write can start out with simple, modest needs. At first, maybe a basic RDBMS will suffice. But, as your needs change and grow, it’s nice to know that, without too much code refactoring, you can change around to match.

Today’s approach does this – allowing for a nearly transparent data source, which the model class knows nothing about. It ensures that irrespective of where the information comes from, it will be transformed so that the model class will be able to use it.

How Does It Work?

TableGateway to Database Model mapping

Simple analogy of Datasources, TableGateways, Hydrators, and Models in Zend Framework 2

In a nutshell here’s how it works. Firstly a TableGateway class performs the specific interaction with the datasource, such as fetching all records, deleting, adding and updating records. In this case it is a MySQL 5 database. The, the hydrator class maps (and transforms where applicable) the information retrieved from the TableGateway to the Model class.

In the getServiceConfig method in our Module class, the two are bound together and the Hydrator is configured. Finally, in a controller action, we can then access the datasource, querying records, auto-populating our Model and then iterate over the records. Sound good? Great!

Let’s get going!

The Table Gateway Class

Here we have the TableGateway class. I’ve called it UserTable as it is responsible for managing information in a user table. I’ve kept it simple and just focused on the retrieving of records skipping over the remainder of the CRUD operations.

In the constructor, we passing in a TableGateway object, which will be made available in the ServiceManager configuration. This provides us with simple access to the database. In the fetchAll function, we retrieve a result set by calling the select method on the TableGateway object.

The calls to buffer and next aren’t strictly necessary – but have been included as, in the list action, a Paginator object is returned. Errors will be thrown if these two methods aren’t called. Not much to it – right?

namespace MaltBlueCore\Model;

use Zend\Db\TableGateway\TableGateway;

class UserTable 
{
    protected $tableGateway;

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

    public function fetchAll()
    {
        $resultSet = $this->tableGateway->select();
        $resultSet->buffer();
        $resultSet->next();
        return $resultSet;
    }
}

The Model Class

Now we have the User class. As you can see, this class has no code that relates to any kind of datasource or database. Here, we’re only concerned about the data that we want to work with – in this case a:

  • user id
  • name
  • active flag
  • record creation date

The exchangeArray method is a common mention in ZF2 for passing in a dataset, such as an array in this case and auto-populating the object. You can see that I’ve set all the properties from the respective array key. Nice clean and simple.

namespace MaltBlueCore\Model;

class User
{
    public $userId;
    public $userName;
    public $userActive;
    public $createdDate;

    public function exchangeArray($data)
    {
        if (isset($data['userName'])) {
            $this->userName = $data['userName'];
        } else {
            $this->userName = null;
        }

        if (isset($data['userActive'])) {
            $this->userActive = $data['userActive'];
        } else {
            $this->userActive = null;
        }

        if (isset($data['userId'])) {
            $this->userId = $data['userId'];
        } else {
            $this->userId = null;
        }

        if (isset($data['createdDate'])) {
            $this->createdDate = $data['createdDate'];
        } else {
            $this->createdDate = null;
        }
    }

    public function getArrayCopy()
    {
        return get_object_vars($this);
    }
}

The Hydrator

The Hydrator is the key aspect of the setup. It’s what maps the database field names on one side, to the entity properties on the other. However, it doesn’t store this information internally – as you’ll see. I went for a more generic approach, one class which could be applied to any table.

What this does is allow for an array of Column Names -> Entity Properties to be passed in as an array and, when its hydrate method is called, will transfer the respective information from the datasource to the model object.

namespace MaltBlueCore\Hydrator;

use ReflectionMethod;
use Traversable;
use Zend\Stdlib\Exception;
use Zend\Stdlib\Hydrator\AbstractHydrator;
use Zend\Stdlib\Hydrator\HydratorOptionsInterface;

class TableEntityMapper 
    extends AbstractHydrator 
    implements HydratorOptionsInterface
{
    protected $_dataMap = true;

    public function __construct($map)
    {
        parent::__construct();    
        $this->_dataMap = $map;
    }

    public function setOptions($options)
    {
        return $this;
    }

    public function extract($object) {}

In the hydrate method, we pass in the datasource and the model. If the model is not an object, we throw a BadMethodCallException. If it is, we proceed and iterate over the data available.

If there is a mapping available, we use that to determine which field in the model matches with which field in the datasource. If it doesn’t (if no mapping is required), we set the property directly. Any unknown or missing properties are silently skipped over.

    public function hydrate(array $data, $object)
    {
        if (!is_object($object)) {
            throw new Exception\BadMethodCallException(sprintf(
                '%s expects the provided $object to be a PHP object)', 
                __METHOD__
            ));
        }

        foreach ($data as $property => $value) {
            if (!property_exists($this, $property)) {
                if (in_array($property, array_keys($this->_dataMap))) {
                    $_prop = $this->_dataMap[$property];
                    $object->$_prop = $value;
                } else {
                    // unknown properties are skipped
                }
            } else {
                $object->$property = $value;
            }
        }

        return $object;
    }
}

The Module Service Configuration

In the Module configuration, is where we tie everything together. I’ve been writing about the ServiceManager previously and I’ve listed some excellent links in the further reading section, which I encourage you to check out. Without this component, the rest really would not be possible.

Firstly we register an object in the factories list, which initialises the UserTable model we covered earlier, passing it the TableGateway object. Following that, we have the configuration for the TableGateway object. I have much to thank Evan Coury for in the approach that I’ve taken here.

What this does is to retrieve the application database adapter and initialise an instance of the hydrator that we previously covered, where we provide it with the mapping array; the table column names on the left and the model properties on the right.

We then provide the User model as the prototype to use with the HydratingResultSet object. If you’re not familiar with it, the HydratingResultSet is a truly amazing part of Zend Framework 2.

I’ll not try an invent the wheel and quote directly from the manual:

Zend\Db\ResultSet\HydratingResultSet is a more flexible ResultSet object that allows the developer to choose an appropriate “hydration strategy” for getting row data into a target object.

While iterating over results, HydratingResultSet will take a prototype of a target object and clone it once for each row. The HydratingResultSet will then hydrate that clone with the row data.

In essence, we provide the model, data and hydrator and it takes care of the rest. Following that, we return a new TableGateway object, specifying the underlying table, tbluser, that will be the source of the data, the database adapter and the resultset object we’ve just initialised.

Now, we have the two sides of the equation beautifully joined. Should the datasource change, the table name, the properties or column names, we only need make a slight adjustment here. No other classes or code need be touched.

public function getServiceConfig()
{
    return array(
        'factories' => array(
            'MaltBlueAdmin\Model\UserTable' =>  function($sm) {
                $tableGateway = $sm->get('UserTableGateway');
                $table = new UserTable($tableGateway);
                return $table;
            },
            'UserTableGateway' => function ($sm) {
                $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
                $hydrator = new \MaltBlueCore\Hydrator\TableEntityMapper(
                    array(
                        'UserID' => 'userId',
                        'UserName' => 'userName',
                        'StatusID' => 'userActive',
                        'CreatedOn' => 'createdDate'
                    ));
                $rowObjectPrototype = new \MaltBlueCore\Model\User;
                $resultSet = new \Zend\Db\ResultSet\HydratingResultSet(
                    $hydrator, $rowObjectPrototype
                );
                return new TableGateway(
                    'tbluser', $dbAdapter, null, $resultSet
                );
            }
        )
    );
}

The Controller

Ok, now we show how to use it. In our controller, we have a list action, which, as the name implies, will retrieve a list of records, which we will iterate over.

namespace MaltBlueManagementAdmin\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use MaltBlueCore\Model\User;
use MaltBlueCore\Form\AddUserForm;
use MaltBlueCore\Form\EditUserForm;

class userController extends AbstractActionController
{
    protected $userTable;
    protected $_createUserForm;

    public function listAction()
    {

Firstly, we retrieve a copy of the userTable object through the Service Locator. We then call the fetchAll method, retrieving all rows from the table. I’ve used a filter iterator and a paginator to make for a more juicy example.

The filter iterator will only return records where the userActive field is set to “active”. All others will be skipped. This iterator is then passed to the Zend\Paginator object, a few properties are set on it and is returned in the ViewModel, ready to be iterated over in the view script.

        $sm = $this->getServiceLocator();
        $userTable = new \MaltBlueCore\Model\UserTable(
            $sm->get('userTableGateway')
        );

        $filterIterator = new StatusFilterIterator(
            $userTable->fetchAll(), "active"
        );
        $paginator = new Paginator(new Iterator($filterIterator));

        $paginator->setCurrentPageNumber(
            $this->params()->fromRoute('page')
        );

        $paginator->setItemCountPerPage(
            $this->params()->fromRoute('perPage', 10)
        );

        return new ViewModel(array(
            'paginator' => $paginator,
            'status' => $this->params()->fromRoute('status')
        ));
    }
}

Conclusion

So, there we have it. With, only a little bit of code, we’ve created a model that is able to interact with a variety of data sources, yet avoid coupling to tightly at the same time. If we move from MySQL to PostgreSQL or Redis, then the requisite parts can be changed to suit.

Now, I’m not as experienced with hydrators, data mappers and the table gateway pattern as others. So I’d love to get your feedback. Tell me where this approach can be improved. Are there other aspects of ZF2 that can take it further – such as Strategies by Jurian Sluiman.

Intermediate Tutorial
  • AntonZelenski

    Do I understand right that Hydrator is necessary only in case when database column names are not the same as fields of model?Otherwise we can just $resultset->setArrayObjectPrototype( $some_empty_instatiated_object ); and data will be mapped to object using exchangeArray method. Am I right?

    • http://www.matthewsetter.com/ maltblue

      @AntonZelenski you’re likely very right. My intent here is to make it relatively painless to map content from any datasource through to the Model, requiring next to no changes on either end, only in the mapper.

      • AntonZelenski

        @maltblue Thank you for excelent explanation!

        • http://www.matthewsetter.com/ maltblue

          @AntonZelenski  Anytime Anton. Glad you found it helpful.

  • OptimusMarlboro

    In this scenario, I assume class variables must be public. Is there another type of hydrator that will work with protected/private class variables?

    • http://www.matthewsetter.com/ maltblue

      @OptimusMarlboro I’m not aware of one specifically. But will check. You could also use reflection. My wifi/3G isn’t the best right now. When I have more coverage, I’ll provide a better answer.

  • Pingback: Hydratoren, Models und das TableGateway Entwurfsmuster - Zend Framework Magazin

  • MPV3

    How would you handel the case where the data type differs between the database and the model? For example 1/0 in DB and true/false in model.

    • http://www.matthewsetter.com/ maltblue

      @MPV3

    • http://www.matthewsetter.com/ maltblue

      @MPV3 in this simple example you could update the exchangeArray function, though this may increase coupling with the data source or require it to have too much knowledge about it. Thinking about it further, maybe the TableEntityMapper via the config array could be extended to provide more information that could be used down the line as well. This might provide the best of both worlds. I’ll look into it and put a solution on GitHub when I’m back from holidays.
      Thanks for the suggestion. Really got me thinking.

  • GeeH

    I’m confused as to why you are using a hydrator, and also have exchangeArray method in your entity. The whole point of the hydrator is to hydrate and dehydrate your entities from and to arrays. Indeed, you are using the supplied HydratorInterface which has a requirement for an “extract” method who’s job is to convert the hydrated entity back into an array format. 
     
    The idea of hydrators is to keep your entities as clean as possible, and by including the exchangeArray method in your entity you are actually not decoupling the data store from it’s conversion methods. I would personally use protected methods in my entity (or model as you’re calling it), and add public getters and setters. You could then use the bundled ClassMethods hydrator (https://github.com/zendframework/zf2/blob/master/library/Zend/Stdlib/Hydrator/ClassMethods.php) to hydrate and dehydrate your entity without re-inventing the wheel.

    • http://www.matthewsetter.com/ maltblue

      @GeeH thanks for the thorough comment. It’s not the most elegant solution that I’ve presented, I agree. And both the link and description you’ve provided really helps me, personally, learn so much more. A damn good topic to be passionate about, wouldn’t you agree?
      The basis of my post though, was from a discussion I read through lead by Evan Coury. Unfortunately I don’t have the link handy but will try and find it. It seemed to indicate that this was a fair approach to take. I’ll have a better read through the ClassMethods class and increase my understanding.

      • GeeH

        @maltblue  It’s a great discussion topic, and I learned my hydrator knowledge from Evan, starting with this post: http://blog.evan.pro/zf2-tablegateway-hydration. It’s a heated topic of discussion in the IRC channel, with most people agreeing that the more you can decouple your data layer from the code that populates it.

        • http://www.matthewsetter.com/ maltblue

          @GeeH it sure was from the post that Evan was involved in. Very heated as I remember. So much to learn, so I’m sure I’ll update this example as I continue to learn more of the Framework and fundamental concepts underpinning it.

        • http://www.matthewsetter.com/ maltblue

          For what it’s worth, I don’t see myself as an expert, not yet anyways. But keen to give what I’ve learned and keep making those examples better the more that I learn. Thanks for helping out with solid feedback.