Skip to content

Instantly share code, notes, and snippets.

@webmozart
Created December 16, 2010 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webmozart/743439 to your computer and use it in GitHub Desktop.
Save webmozart/743439 to your computer and use it in GitHub Desktop.
<?php
class OptionSupport
{
protected static $definitions = array();
public static function getDefinition($class)
{
if (!isset(self::$definitions[$class])) {
self::$definitions[$class] = new OptionDefinition($class);
}
return self::$definitions[$class];
}
public static function initialize($object, array $options)
{
if (!is_object($object)) {
throw new LogicException('Can only set options on objects');
}
if (count($options) === 0) {
return;
}
self::getDefinition(get_class($object))->setOptions($object, $options);
}
}
class OptionDefinition
{
protected $class;
protected $requiredOptions = array();
protected $options = array();
public function __construct($class)
{
$this->class = $class;
$parentClass = get_parent_class($class);
if ($parentClass) {
$parentDefinition = OptionSupport::getDefinition($parentClass);
$this->requiredOptions = $parentDefinition->requiredOptions;
$this->options = $parentDefinition->options;
}
try
{
if (isset($class::$options)) {
foreach ($class::$options as $option) {
if (!isset($this->options[$option])) {
$reflProperty = new ReflectionProperty($class, $option);
$reflProperty->setAccessible(true);
$this->options[$option] = $reflProperty;
}
}
}
if (isset($class::$requiredOptions)) {
foreach ($class::$requiredOptions as $option) {
if (!isset($this->options[$option])) {
$reflProperty = new ReflectionProperty($class, $option);
$reflProperty->setAccessible(true);
$this->options[$option] = $reflProperty;
}
$this->requiredOptions[$option] = true;
}
}
} catch (\ReflectionException $e) {
throw new \LogicException('Invalid option definition: ' . $e->getMessage(), 0, $e);
}
}
public function setOptions($object, array $options)
{
if (!$object instanceof $this->class) {
throw new \LogicException(sprintf('Cannot add options to an object of the wrong type. Expected %s, got %s', $this->class, get_class($object)));
}
// check option names
if ($diff = array_diff_key($options, $this->options)) {
throw new \LogicException(sprintf('%s does not support the following options: "%s".', $this->class, implode('", "', array_keys($diff))));
}
// check required options
if ($diff = array_diff_key($this->requiredOptions, $options)) {
throw new \LogicException(sprintf('%s requires the following options: \'%s\'.', $this->class, implode('", "', array_keys($diff))));
}
foreach ($options as $option => $value) {
$this->options[$option]->setValue($object, $value);
}
}
}
class OptionSupport2
{
protected static $definitions = array();
public static function getDefinition($class)
{
if (!isset(self::$definitions[$class])) {
self::$definitions[$class] = new OptionDefinition2($class);
}
return self::$definitions[$class];
}
public static function initialize($object, array $options)
{
if (!is_object($object)) {
throw new LogicException('Can only set options on objects');
}
if (count($options) === 0) {
return;
}
self::getDefinition(get_class($object))->setOptions($object, $options);
}
}
class OptionDefinition2
{
protected $class;
protected $requiredOptions = array();
protected $options = array();
public function __construct($class)
{
$this->class = $class;
$parentClass = get_parent_class($class);
if ($parentClass) {
$parentDefinition = OptionSupport2::getDefinition($parentClass);
$this->requiredOptions = $parentDefinition->requiredOptions;
$this->options = $parentDefinition->options;
}
try
{
if (method_exists($class, 'getOptions')) {
foreach ($class::getOptions() as $option) {
if (!isset($this->options[$option])) {
$reflProperty = new ReflectionProperty($class, $option);
$reflProperty->setAccessible(true);
$this->options[$option] = $reflProperty;
}
}
}
if (method_exists($class, 'getRequiredOptions')) {
foreach ($class::getRequiredOptions() as $option) {
if (!isset($this->options[$option])) {
$reflProperty = new ReflectionProperty($class, $option);
$reflProperty->setAccessible(true);
$this->options[$option] = $reflProperty;
}
$this->requiredOptions[$option] = true;
}
}
} catch (\ReflectionException $e) {
throw new \LogicException('Invalid option definition: ' . $e->getMessage(), 0, $e);
}
}
public function setOptions($object, array $options)
{
if (!$object instanceof $this->class) {
throw new \LogicException(sprintf('Cannot add options to an object of the wrong type. Expected %s, got %s', $this->class, get_class($object)));
}
// check option names
if ($diff = array_diff_key($options, $this->options)) {
throw new \LogicException(sprintf('%s does not support the following options: "%s".', $this->class, implode('", "', array_keys($diff))));
}
// check required options
if ($diff = array_diff_key($this->requiredOptions, $options)) {
throw new \LogicException(sprintf('%s requires the following options: \'%s\'.', $this->class, implode('", "', array_keys($diff))));
}
foreach ($options as $option => $value) {
$this->options[$option]->setValue($object, $value);
}
}
}
class A_OptionSupport
{
static public $options = array(
'a',
'b',
'c',
'd',
);
private $a = 1;
private $b = 2;
private $c;
private $d;
public function __construct(array $options)
{
OptionSupport::initialize($this, $options);
}
}
class B_OptionSupport extends A_OptionSupport
{
}
class C_OptionSupport extends B_OptionSupport
{
}
class A_OptionSupport2
{
static protected $options = array(
'a',
'b',
'c',
'd',
);
static public function getOptions()
{
return static::$options;
}
private $a = 1;
private $b = 2;
private $c;
private $d;
public function __construct(array $options)
{
OptionSupport2::initialize($this, $options);
}
}
class B_OptionSupport2 extends A_OptionSupport2
{
}
class C_OptionSupport2 extends B_OptionSupport2
{
}
define('COUNT', 4);
// benchmark OptionSupport
echo "\nOptionSupport\n";
echo "-------------\n";
$start1 = microtime(true);
new C_OptionSupport(array('c' => 1));
echo 'Object 1: ' . (microtime(true) - $start1) . "ms\n";
$start2 = microtime(true);
for ($i = 0; $i < (COUNT - 1); ++$i) {
$class = $i % 2 == 0 ? 'B_OptionSupport' : 'C_OptionSupport';
new $class(array('c' => 1));
}
echo 'Object 2-' . COUNT . ': ' . (microtime(true) - $start2) . "ms\n";
echo 'Total: ' . (microtime(true) - $start1) . "ms\n";
// benchmark OptionSupport2
echo "\nOptionSupport2\n";
echo "--------------\n";
$start1 = microtime(true);
new C_OptionSupport2(array('c' => 1));
echo 'Object 1: ' . (microtime(true) - $start1) . "ms\n";
$start2 = microtime(true);
for ($i = 0; $i < (COUNT - 1); ++$i) {
$class = $i % 2 == 0 ? 'B_OptionSupport2' : 'C_OptionSupport2';
new $class(array('c' => 1));
}
echo 'Object 2-' . COUNT . ': ' . (microtime(true) - $start2) . "ms\n";
echo 'Total: ' . (microtime(true) - $start1) . "ms\n";
OptionSupport
-------------
Object 1: 0.000196218490601ms
Object 2-4: 0.000143766403198ms
Total: 0.000360012054443ms
OptionSupport2
--------------
Object 1: 0.000185966491699ms
Object 2-4: 0.000142097473145ms
Total: 0.000349044799805ms
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment