Skip to content

Instantly share code, notes, and snippets.

@attilammagyar
Created August 30, 2011 20:31
Show Gist options
  • Save attilammagyar/1181943 to your computer and use it in GitHub Desktop.
Save attilammagyar/1181943 to your computer and use it in GitHub Desktop.
PHP: user defined readonly, type-checked, callback or calculated properties
<?php
class MyObject
{
public function __construct()
{
$this->properties = array();
}
public function __get($property_name)
{
$callback = array($this, $property_name);
if (is_callable($callback))
return $callback;
$property = new PropertyDescriptor($this, $property_name);
$this->validateProperty($property);
return call_user_func($property->getter);
}
public function __set($property_name, $value)
{
$property = new PropertyDescriptor($this, $property_name);
$this->validateWritableProperty($property);
return call_user_func($property->setter, $value);
}
private function validateProperty(PropertyDescriptor $property)
{
if (!$this->isValidProperty($property))
{
throw new ErrorException(
"Unknown property; name={$property->name}"
);
}
}
private function isValidProperty(PropertyDescriptor $property)
{
$property_name = $property->name;
if (array_key_exists($property_name, $this->properties))
return $this->properties[$property_name];
if (!is_callable($property->getter))
return false;
if (!$this->reflection)
$this->reflection = new ReflectionClass($this);
$getter_method_name = $property->getter[1];
$getter_method = $this->reflection->getMethod($getter_method_name);
$doc_comment = $getter_method->getDocComment();
$is_valid = strpos($doc_comment, "@property $property_name") !== false;
$this->properties[$property_name] = $is_valid;
return $is_valid;
}
private function validateWritableProperty(PropertyDescriptor $property)
{
$this->validateProperty($property);
if (!is_callable($property->setter))
{
throw new ErrorException(
"Attempting to write a readonly property; name={$property->name}"
);
}
}
private $reflection;
private $properties;
}
class PropertyDescriptor
{
public function __construct(MyObject $owner_object, $property_name)
{
$this->name = (string)$property_name;
$method_name_suffix = ucfirst($this->name);
$this->getter = array($owner_object, "get$method_name_suffix");
$this->setter = array($owner_object, "set$method_name_suffix");
}
public $name;
public $getter;
public $setter;
}
?>
<?php
require_once 'MyObject.php';
class RationalNumber extends MyObject
{
public function __construct($numerator = 0, $denominator = 1)
{
parent::__construct();
$this->numerator = $numerator;
$this->denominator = $denominator;
}
public function multiply(RationalNumber $that)
{
$this->numerator *= $that->numerator;
$this->denominator *= $that->denominator;
}
/**
* @property numerator
*/
protected function getNumerator()
{
return $this->_numerator;
}
protected function setNumerator($numerator)
{
$this->_numerator = (int)$numerator;
}
/**
* @property denominator
*/
protected function getDenominator()
{
return $this->_denominator;
}
protected function setDenominator($denominator)
{
$d = (int)$denominator;
if ($d == 0)
throw new ErrorException('Denominator must be non-zero!');
$this->_denominator = $d;
}
/**
* @property value
*/
protected function getValue()
{
return ((float)$this->_numerator) / ((float)$this->_denominator);
}
private $_numerator = 0;
private $_denominator = 1;
}
function property_test()
{
$rational_number = new RationalNumber();
$rational_number->numerator = 1;
$rational_number->numerator *= 2;
echo 'Setter might force property type: ';
$rational_number->denominator = "4hello";
var_dump($rational_number->denominator);
echo 'Calculated property: ';
var_dump($rational_number->value);
echo 'Setter might do error checks and validation: ';
try {
$rational_number->denominator = "0";
} catch (Exception $e) { echo $e->getMessage() . "\n"; }
echo 'Readonly properties can be made: ';
try {
$rational_number->value = 42;
} catch (Exception $e) { echo $e->getMessage() . "\n"; }
echo 'Accidentally using undefined properties can cause trouble: ';
try {
$rational_number->numreator = 42; // typo in property name!
} catch (Exception $e) { echo $e->getMessage() . "\n"; }
echo 'A callback method can be passed along as if it were a property: ';
$numbers = array(
new RationalNumber(1, 1),
new RationalNumber(1, 3),
new RationalNumber(1, 5)
);
array_walk($numbers, $rational_number->multiply);
echo "{$rational_number->value}\n";
}
property_test();
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment