A common usage, at least for extract(), has been to allow an associative array to be passed to a function for use as named optional parameters, however, there is a lot wrong with dumping an array into the symbol table and then checking if what you want exists or not.
I have designed a pattern that allows an object to accept and address public level optional named arguments without the problems that could be associated with the compact()/extract() pattern. This pattern does require at least PHP 5.3 (uses late static binding) and overloads __get/__set preventing the child class from doing so.
Usage
Possible optional keys are defined as class constants, if one is addressed or set that is undefined an exception will be thrown. Instead of using extract(), the inherited SetOptionalProperties($array) method should be called to set the values. The value’s can be addressed by it’s constants name like it’s a property, like $instance->ConstantName. Default values can be given by overloading the protected static $_defaultOptions member. The example shows this in with more detail:
Example
class MyClass extends Options {
public $Property;
const MyOption = "MyOption";
const AnotherOption = "AnotherOption";
protected static $_defaultOptions = array(self::AnotherOption = "default value!");
public function __construct($property, $options = array()) {
$this->Property = $property;
$this->SetOptionalProperties($options); // this allows Options to access the optional values.
}
}
$instance = new MyClass("property's value", array(MyClass::MyOption = "my option value!"));
echo "MyOption: '{$instance->MyOption}', AnotherOption: '{$instance->AnotherOption}', Property: '{$instance->Property}'";
Output:
MyOption: 'my option value!', AnotherOption: 'default value!', Property: 'property's value'
Class
abstract class Options {
/// The array which stores the optional values
private $_options = array();
/// This array should be overloaded by child classes if default values are going to be used.
protected static $_defaultOptions = array();
/// Use Options::$AllowUndefinedProperties = true; to prevent exceptions from being thrown when an undefined
/// property is set or fetched.
public static $AllowUndefinedProperties = false;
public function &__get($name) {
$resolvedName = $this->ResolveOptionName($name);
// Do we have a value? Use the isset/array_key_exists fall through to speed up access to existing non-null entries.
if (!isset($this->_options[$resolvedName]) && !array_key_exists($resolvedName, $this->_options)) {
// Do we have a default value?
if (array_key_exists($resolvedName, static::$_defaultOptions))
$this->_options[$resolvedName] = static::$_defaultOptions[$resolvedName];
else
$this->_options[$resolvedName] = null;
}
return $this->_options[$resolvedName];
}
public function __set($name, $value) {
$resolvedName = $this->ResolveOptionName($name);
return $this->_options[$resolvedName] = $value;
}
public function __isset($name) {
$resolvedName = $this->ResolveOptionName($name);
return isset($this->_options[$resolvedName]);
}
public function __unset($name) {
$resolvedName = $this->ResolveOptionName($name);
unset($this->_options[$resolvedName]);
}
/**
* Set the optional properties for the class.
* @param $options A [ClassConstant] = Value indexed array of optional properties.
*/
protected function SetOptionalProperties(array $options) {
$this->_options = $options;
}
/// Resolve any aliases name to the proper name.
private function ResolveOptionName($name) {
$constant = "static::". $name;
if (defined($constant))
return constant($constant);
else if (!self::$AllowUndefinedProperties)
throw new Exception("Undefined property or option '$name' being set in ". get_called_class());
}
}