The Applicator Design Pattern?
I have been learning more and more about Zend Framework over the past few months. The more I lean, the more I appreciate its loosely couple nature. For example, most ZF classes will work with minimal configuration out of the box, and any configuration that is necessary can be done by either passing in a configuration array (or Zend_Config object), or by using the object’s setter/getter methods.
For example, both these code snippets essentially do the same thing:
<?
$textElement = new Zend_Form_Element_Text();
$textElement->setLabel('My text field:');
?>
<?
$textElement = new Zend_Form_Element_Text(array(‘label’ => 'My text field:’));
?>
I like this two-pronged approach as it allows for simple configuration of objects within code, as well as making it easy to pass in a predefined set of configuration options (e.g. from an INI file)
So I decided to write a simple utility class that would make it simple to implement this approach in my own classes. This utility is the Applicator class:
<?php
/**
* A utility for applying a array of properties to an object
*
* This class can be used to apply an associative array of properties to
* an object. Each key should be a property name for which its value should
* be applied.
*
* This will also automate the use of setter methods where
* available. For example, if $properties is an array in the form
* array('myProp', 123), then the following process would be performed:
*
* - Check to see if a method name 'setMyProp' can be called on the
* given object.
* - If the method can be called, then call it and pass the value as
* the sole parameter.
* - If the method cannot be called then set the 'myProp' property directy
*
* It is also worth noting that, unless the object extends OW_Applicator, the
* getter and setter methods will need to be publically visable in order to
* be used.
*
*/
class OW_Applicator {
protected $_useOnlyMethods = true;
/**
* Set the 'useOnlyMethods' flag
*
* If the boolean useOnlyMethods flag is set to true then the
* applicator will only use setter methods to set properties.
* If it is set to false then properties will be set where
* setter methods cannot be found.
*
* @param boolean $value
* @return OW_Applicator Provides a fluent interface
*/
public function setUseOnlyMethods($value) {
$this->_useOnlyMethods = (boolean)$value;
return $this;
}
/**
* Get the 'useOnlyMethods' flag
*
* See setUseOnlyMethods() for further details
*
* @return boolean
*/
public function getUseOnlyMethods() {
return $this->_useOnlyMethods;
}
/**
* Apply $properties to $object using getter and setter methods where possible
*
* See the OW_Applicator class description for further details
*
* @param array|Zend_Config $properties An associative array of properties, or an instance of Zend_Config
* @param object $object The object to which $properties are to be applied
* @return object Will return $object
*/
public function apply($properties, $object) {
if($properties instanceof Zend_Config) {
$properties = $properties->toArray();
}
if(!is_array($properties)) {
throw new OW_InvalidArgumentException('$properties should be an associative array');
}
if(!is_object($object)) {
throw new OW_InvalidArgumentException('$object should be an object');
}
foreach ($properties as $name => $value) {
$object = $this->applyProperty($name, $value, $object);
}
return $object;
}
/**
* Apply property $name with $value to $object
*
* Will use setter and getter methods where possible
*
* Also see useOnlyMethods()
*
* @param string $name The name of the property to set
* @param mixed $value The value of the property
* @param object $object The object to which $properties are to be applied
* @return object Will return $object
*/
protected function applyProperty($name, $value, $object) {
$setMethod = 'set' . ucfirst($name);
if(method_exists($object, $setMethod)) {
$object->$setMethod($value);
} else if(!$this->_useOnlyMethods) {
$object->$name = $value;
}
return $object;
}
}
The part we are interested in is the apply() method. This accepts an associative array of properties (or a Zend_Config object) as the first parameter, and an object as the second parameter. The apply() method will then call the associated setter method for each key/value pair in the first parameter.
Example usage:
<?
class ConfigurableClass {
protected $_myValue;
public function __construct($config) {
$applicator = new OW_Applicator();
$applicator->apply($config, $this);
}
public function getMyValue() {
return $this->_myValue;
}
public function setMyValue($myValue) {
$this->_myValue = $myValue;
return $this;
}
}
$obj = new ConfigurableClass(array('myValue' => 'foo'));
echo $obj->getMyValue(); //echos 'foo'
?>
I like to work this way because:
- It provides two ways to configure your class without code duplication
- It ensures that there are always matching setter methods for each configuration option
- There is less setup code required in the constructor
- Names of configuration options do not require separate documentation as they are directly related to the setter method name.
I am sure that this has been done before, and it may be a known design pattern, but a cursory glance around Google yielded no obvious results.
No comments yet
Leave a reply
