It is often the case that runtime conditions force the developer to define several ways of doing the same thing. Traditionally, this involved a massive if/elseif/else block of commands. You would then either have to define large blocks of logic inside the if statement, or create a series of functions or methods to enable the different approaches. The strategy pattern attempts to formalize this process by having the primary class encapsulate a series of sub-classes that represent different approaches to solve the same problem.
GetSet hydrator class defined previously as a strategy. We will define a primary Application\Generic\Hydrator\Any class, which will then consume strategy classes in the Application\Generic\Hydrator\Strategy namespace, including GetSet, PublicProps, and Extending.namespace Application\Generic\Hydrator;
use InvalidArgumentException;
use Application\Generic\Hydrator\Strategy\ {
GetSet, PublicProps, Extending };
class Any
{
const STRATEGY_PUBLIC = 'PublicProps';
const STRATEGY_GET_SET = 'GetSet';
const STRATEGY_EXTEND = 'Extending';
protected $strategies;
public $chosen;$strategies property:public function __construct()
{
$this->strategies[self::STRATEGY_GET_SET] = new GetSet();
$this->strategies[self::STRATEGY_PUBLIC] = new PublicProps();
$this->strategies[self::STRATEGY_EXTEND] = new Extending();
}addStrategy() method that allows us to overwrite or add new strategies without having to recode the class:public function addStrategy($key, HydratorInterface $strategy)
{
$this->strategies[$key] = $strategy;
}hydrate() and extract() methods simply call those of the chosen strategy:public function hydrate(array $array, $object)
{
$strategy = $this->chooseStrategy($object);
$this->chosen = get_class($strategy);
return $strategy::hydrate($array, $object);
}
public function extract($object)
{
$strategy = $this->chooseStrategy($object);
$this->chosen = get_class($strategy);
return $strategy::extract($object);
}chooseStrategy(), which takes an object as an argument. We first perform some detective work by way of getting a list of class methods. We then scan through the list to see if we have any getXXX() or setXXX() methods. If so, we choose the GetSet hydrator as our chosen strategy:public function chooseStrategy($object)
{
$strategy = NULL;
$methodList = get_class_methods(get_class($object));
if (!empty($methodList) && is_array($methodList)) {
$getSet = FALSE;
foreach ($methodList as $method) {
if (preg_match('/^get|set.*$/i', $method)) {
$strategy = $this->strategies[self::STRATEGY_GET_SET];
break;
}
}
}chooseStrategy() method, if there are no getters or setters, we next use get_class_vars() to determine if there are any available properties. If so, we choose PublicProps as our hydrator:if (!$strategy) {
$vars = get_class_vars(get_class($object));
if (!empty($vars) && count($vars)) {
$strategy = $this->strategies[self::STRATEGY_PUBLIC];
}
}Extending hydrator, which returns a new class that simply extends the object class, thus making any public or protected properties available:if (!$strategy) {
$strategy = $this->strategies[self::STRATEGY_EXTEND];
}
return $strategy;
}
}Application\Generic\Hydrator\Strategy namespace.Application\Generic\Hydrator\Any:namespace Application\Generic\Hydrator\Strategy;
interface HydratorInterface
{
public static function hydrate(array $array, $object);
public static function extract($object);
}GetSet hydrator is exactly as defined in the previous two recipes, with the only addition being that it will implement the new interface:namespace Application\Generic\Hydrator\Strategy;
class GetSet implements HydratorInterface
{
public static function hydrate(array $array, $object)
{
// defined in the recipe:
// "Creating an Array to Object Hydrator"
}
public static function extract($object)
{
// defined in the recipe:
// "Building an Object to Array Hydrator"
}
}namespace Application\Generic\Hydrator\Strategy;
class PublicProps implements HydratorInterface
{
public static function hydrate(array $array, $object)
{
$propertyList= array_keys(
get_class_vars(get_class($object)));
foreach ($propertyList as $property) {
$object->$property = $array[$property] ?? NULL;
}
return $object;
}
public static function extract($object)
{
$array = array();
$propertyList = array_keys(
get_class_vars(get_class($object)));
foreach ($propertyList as $property) {
$array[$property] = $object->$property;
}
return $array;
}
}Extending, the Swiss Army knife of hydrators, extends the object class, thus providing direct access to properties. We further define magic getters and setters to provide access to properties.hydrate() method is the most difficult as we are assuming no getters or setters are defined, nor are the properties defined with a visibility level of public. Accordingly, we need to define a class that extends the class of the object to be hydrated. We do this by first defining a string that will be used as a template to build the new class:namespace Application\Generic\Hydrator\Strategy;
class Extending implements HydratorInterface
{
const UNDEFINED_PREFIX = 'undefined';
const TEMP_PREFIX = 'TEMP_';
const ERROR_EVAL = 'ERROR: unable to evaluate object';
public static function hydrate(array $array, $object)
{
$className = get_class($object);
$components = explode('\\', $className);
$realClass = array_pop($components);
$nameSpace = implode('\\', $components);
$tempClass = $realClass . self::TEMP_SUFFIX;
$template = 'namespace '
. $nameSpace . '{'
. 'class ' . $tempClass
. ' extends ' . $realClass . ' 'hydrate() method, we define a $values property, and a constructor that assigns the array to be hydrated into the object as an argument. We loop through the array of values, assigning values to properties. We also define a useful getArrayCopy() method, which returns these values if needed, as well as a magic __get() method to simulate direct property access:. '{ '
. ' protected $values; '
. ' public function __construct($array) '
. ' { $this->values = $array; '
. ' foreach ($array as $key => $value) '
. ' $this->$key = $value; '
. ' } '
. ' public function getArrayCopy() '
. ' { return $this->values; } '__get() method, which simulates direct variable access as if they were public:. ' public function __get($key) '
. ' { return $this->values[$key] ?? NULL; } '__call() method, which simulates getters and setters:. ' public function __call($method, $params) '
. ' { '
. ' preg_match("/^(get|set)(.*?)$/i", '
. ' $method, $matches); '
. ' $prefix = $matches[1] ?? ""; '
. ' $key = $matches[2] ?? ""; '
. ' $key = strtolower(substr($key, 0, 1)) '
. ' substr($key, 1); '
. ' if ($prefix == "get") { '
. ' return $this->values[$key] ?? NULL; '
. ' } else { '
. ' $this->values[$key] = $params[0]; '
. ' } '
. ' } '
. '} '
. '} // ends namespace ' . PHP_EOL. 'namespace { '
. 'function build($array) '
. '{ return new ' . $nameSpace . '\\'
. $tempClass . '($array); } '
. '} // ends global namespace '
. PHP_EOL;hydrate() method, we execute the completed template using eval(). We then run the build() method defined just at the end of the template. Note that as we are unsure of the namespace of the class to be populated, we define and call build() from the global namespace:try {
eval($template);
} catch (ParseError $e) {
error_log(__METHOD__ . ':' . $e->getMessage());
throw new Exception(self::ERROR_EVAL);
}
return \build($array);
}extract() method is much easier to define as our choices are extremely limited. Extending a class and populating it from an array using magic methods is easily accomplished. The reverse is not the case. If we were to extend the class, we would lose all the property values, as we are extending the class, not the object instance. Accordingly, our only option is to use a combination of getters and public properties:public static function extract($object)
{
$array = array();
$class = get_class($object);
$methodList = get_class_methods($class);
foreach ($methodList as $method) {
preg_match('/^(get)(.*?)$/i', $method, $matches);
$prefix = $matches[1] ?? '';
$key = $matches[2] ?? '';
$key = strtolower(substr($key, 0, 1))
. substr($key, 1);
if ($prefix == 'get') {
$array[$key] = $object->$method();
}
}
$propertyList= array_keys(get_class_vars($class));
foreach ($propertyList as $property) {
$array[$property] = $object->$property;
}
return $array;
}
}You can begin by defining three test classes with identical properties: firstName, lastName, and so on. The first, Person, should have protected properties along with getters and setters. The second, PublicPerson, will have public properties. The third, ProtectedPerson, has protected properties but no getters nor setters:
<?php
namespace Application\Entity;
class Person
{
protected $firstName = '';
protected $lastName = '';
protected $address = '';
protected $city = '';
protected $stateProv = '';
protected $postalCode = '';
protected $country = '';
public function getFirstName()
{
return $this->firstName;
}
public function setFirstName($firstName)
{
$this->firstName = $firstName;
}
// be sure to define remaining getters and setters
}
<?php
namespace Application\Entity;
class PublicPerson
{
private $id = NULL;
public $firstName = '';
public $lastName = '';
public $address = '';
public $city = '';
public $stateProv = '';
public $postalCode = '';
public $country = '';
}
<?php
namespace Application\Entity;
class ProtectedPerson
{
private $id = NULL;
protected $firstName = '';
protected $lastName = '';
protected $address = '';
protected $city = '';
protected $stateProv = '';
protected $postalCode = '';
protected $country = '';
}You can now define a calling program called chap_11_strategy_pattern.php, which sets up autoloading and uses the appropriate classes:
<?php
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\Entity\ { Person, PublicPerson, ProtectedPerson };
use Application\Generic\Hydrator\Any;
use Application\Generic\Hydrator\Strategy\ { GetSet, Extending, PublicProps };Next, create an instance of Person and run the setters to define values for properties:
$obj = new Person();
$obj->setFirstName('Li\'lAbner');
$obj->setLastName('Yokum');
$obj->setAddress('1 Dirt Street');
$obj->setCity('Dogpatch');
$obj->setStateProv('Kentucky');
$obj->setPostalCode('12345');
$obj->setCountry('USA');Next, create an instance of the Any hydrator, call extract(), and use var_dump() to view the results:
$hydrator = new Any(); $b = $hydrator->extract($obj); echo "\nChosen Strategy: " . $hydrator->chosen . "\n"; var_dump($b);
Observe, in the following output, that the GetSet strategy was chosen:

Next, you can define an array with the same values. Call hydrate() on the Any instance, and supply a new PublicPerson instance as an argument:
$a = [ 'firstName' => 'Li\'lAbner', 'lastName' => 'Yokum', 'address' => '1 Dirt Street', 'city' => 'Dogpatch', 'stateProv' => 'Kentucky', 'postalCode' => '12345', 'country' => 'USA' ]; $p = $hydrator->hydrate($a, new PublicPerson()); echo "\nChosen Strategy: " . $hydrator->chosen . "\n"; var_dump($p);
Here is the result. Note that the PublicProps strategy was chosen in this case:

Finally, call hydrate() again, but this time supply an instance of ProtectedPerson as the object argument. We then call getFirstName() and getLastName() to test the magic getters. We also access first and last names as direct variable access:
$q = $hydrator->hydrate($a, new ProtectedPerson());
echo "\nChosen Strategy: " . $hydrator->chosen . "\n";
echo "Name: {$q->getFirstName()} {$q->getLastName()}\n";
echo "Name: {$q->firstName} {$q->lastName}\n";
var_dump($q);Here is the last output, showing that the Extending strategy was chosen. You'll also note that the instance is a new ProtectedPerson_TEMP class, and that the protected properties are fully populated:
