Created
August 30, 2011 20:31
-
-
Save attilammagyar/1181943 to your computer and use it in GitHub Desktop.
PHP: user defined readonly, type-checked, callback or calculated properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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