Strategies for hydrators: a practical use case

After using Zend Framework 2 for more than a year, I just found out about strategies for hydrators today. Yes, today and thanks to Michael Gallego who introduced me to this concept. But I think many people underestimate what they can do with hydrators and their strategies, so this post explains why strategies for hydrators are awesome.

Take an entity for example which contains a timestamp. You prefer to type hint on the setter for a DateTime object, for obvious reasons:

public function getDate()
{
    return $this->timestamp;
}

public function setDate(DateTime $timestamp)
{
    $this->timestamp = $timestamp;
    return $this;
}

However, you run into troubles if you bind this entity to a form. By default the POST contains string values, so a date value can be send to the server as “2012-11-06”. The hydrator wants to set this string to your entity, but it fails due to the missing DateTime object. And then the strategy comes to the rescue!

A strategy manipulates (and probably converts) a value during extraction and/or hydration. In this case, it’s perfectly valid to extract the timestamp as a DateTime object. However, it is important to hydrate the value specifically according to the signature: the string value must be converted into a DateTime object. Now we can apply this conversion in a strategy:

namespace MyModule\Hydrator\Strategy;

use DateTime;
use Zend\Stdlib\Hydrator\Strategy\DefaultStrategy;

class DateTimeStrategy extends DefaultStrategy
{
  /**
   * {@inheritdoc}
   *
   * Convert a string value into a DateTime object
   */
  public function hydrate($value)
  {
      if (is_string($value)) {
          $value = new DateTime($value);
      }

      return $value;
  }
}

In this strategy only the hydration part is implemented. If a specific value is hydrated and this value is a string, it must be converted into a DateTime object. The only thing left is to attach this strategy to your hydrator, for the field you want this to be applied. In above case, the property is extracted to/hydrated from a "date" (see the naming getDate() and setDate()). So the strategy must be applied to the "date" field:

use Zend\Stdlib\Hydrator\ClassMethods;
use MyModule\Hydrator\Strategy\DateTimeStrategy;

$hydrator = new ClassMethods;
$hydrator->addStrategy('date', new DateTimeStrategy);

If you now hydrate the entity in the example with a "date" key in the array containing a string, is is perfectly transformed into a DateTime object.

One step further: unset properties via the strategy

You can extend this principle. If you want to clear the timestamp, you want to set the date to null. However, if you have an HTML form, you cannot make a POST indicating null. What happens is you send an empty string. So date is now "".

You can modify the signature of your entity to use the setter to unset the date:

public function setDate(DateTime $timestamp = null)
{
   $this->timestamp = $timestamp;
   return $this;
}

And the hydrator must understand an empty string means converting it to null:

public function hydrate($value)
{
   if (is_string($value) && "" === $value) {
       $value = null;
   } elseif (is_string($value)) {
       $value = new DateTime($value);
   }

   return $value;
}

And voila! Your entity is very clean and the strategy solves all the mess for you.