Skip to content

Instantly share code, notes, and snippets.

@SmiSoft SmiSoft/PropOwner.php
Last active Jul 29, 2018

Embed
What would you like to do?
Automatic read-only and read-write properties support in PHP
<?php
/**
* Automatic read-only and read-write properties support in PHP
*
* Summary:
* protected $r_readonlyProp=12; - an read-only property, read with print($class->readonlyProp);
* write $class->readonlyProp=1; will trigger warning
* protected $rw_readwriteProp=44; - an read-write property, read with print($class->readwriteProp);
* write $class->readwriteProp=1; without restrictions
* protected $rw_getsetProp=7; - a property, that will be read using getter and write using setter.
* Read like print($class->getsetProp); (this will result in print($class->getGetsetProp());)
* Write like $class->getsetProp=123; (this will result in $class->setGetsetProp(123);)
* protected function getGetsetProp(){} - a getter for property getsetProp (first letter uppercased)
* protected function setGetsetProp($value){} - a setter for property getsetProp (first letter uppercased)
*
* In detail:
* Every descendant from this class will have ability to automagically create properties
* (named by following conventions), that may be read-only or read-write, with optional
* getters and setters. Conventions:
* - each property must have "data holder". Currently, there are no support for completely
* "virtual" properties, that have only getter (and, optionally, setter)
* - each property data holder should be "protected" or "private". It has no sense to create
* "public" properties, because user can simply read or write data directly to data-holder.
* - each read-only property must start from "r_" prefix. So, read-only property, named "length"
* must have data-holder named "$r_length". Externally, you should use print($class->length)
* to get information from property, but operator like $class->length=222; will trigger a
* warning. Certainly, internally your class may write data into $this->r_length, ignoring
* that property is read-only.
* - each read-write property must start with "rw_" prefix. For example, a data-holder for
* property "name" should be "$rw_name". Read properties with command like print($class->name)
* and write with $class->name. You may internally use $class->rw_name if your want.
* - read-only and read-write properties may optionally have a getter. It must be a function
* without parameters, named as "getProperty" (property name ucfirsted), returning value of property.
* For example, property, named "$rw_info" must have getter "function getInfo()"
* - read-write properties can optionally have setters. It must be a function with single argument,
* which is new value for property.
*
* Known bug:
* - a trigger error display file and line number twice: first time at place, where actual error happened
* (for example, write-access to read-only property) and second time at line, where trigger_error located.
* I don't know, how to hide second message. While using xdebug, it result in unnesessary stacktrace.
* You may use xdebug_disable() to disable stacktrace.
* @author Python SmiSoft
* @version 1.0.0.0 (29.07.2018)
* @todo completely "virtual" properties, that have no data-holders, only getter (and, optional, setter)
*/
abstract class PropOwner{
public function __isset($name){
if(property_exists($this,$name))
return true;
if(property_exists($this,'rw_'.$name))
return true;
if(property_exists($this,'r_'.$name))
return true;
return false;
}
public function __get($name){
$propName='rw_'.$name;
if(property_exists($this,$propName)){
$getter='get'.ucfirst($name);
if(method_exists($this, $getter))
return $this->$getter();
else
return $this->$propName;
}
$propName='r_'.$name;
if(property_exists($this,$propName)){
$getter='get'.ucfirst($name);
if(method_exists($this, $getter))
return $this->$getter();
else
return $this->$propName;
}
$stack=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,1)[0];
$einfo=extension_loaded('xdebug') && xdebug_is_enabled()?
'Property %s does not exists in file %s on line %d!':
'Property <strong>%s</strong> does not exists in <strong>%s</strong> on line <strong>%s</strong>!';
trigger_error(sprintf($einfo,$name,$stack['file'],$stack['line']),E_USER_ERROR);
return null;
}
// magic access to protected readonly and readwrite properties
public function __set($name, $value){
$propName='rw_'.$name;
if(property_exists($this,$propName)){
$setter='set'.ucfirst($name);
if(method_exists($this,$setter))
$this->$setter($value);
else
$this->$propName=$value;
return;
}
$propName='r_'.$name;
if(property_exists($this,$propName)){
$stack=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,1)[0];
$einfo=extension_loaded('xdebug') && xdebug_is_enabled()?
'Property %s is read-only in file %s on line %d!':
'Property <strong>%s</strong> is read-only in <strong>%s</strong> on line <strong>%s</strong>!';
trigger_error(sprintf($einfo,$name,$stack['file'],$stack['line']),E_USER_WARNING);
return;
}
$stack=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,1)[0];
$einfo=extension_loaded('xdebug') && xdebug_is_enabled()?
'Property %s does not exists in file %s on line %d!':
'Property <strong>%s</strong> does not exists in <strong>%s</strong> on line <strong>%s</strong>!';
trigger_error(sprintf($einfo,$name,$stack['file'],$stack['line']),E_USER_ERROR);
}
}
class TestClass extends PropOwner{
public $regularProp=1;
protected $r_readonlyProp=12;
protected $rw_readwriteProp=44;
protected $rw_getsetProp=7;
protected function getGetsetProp(){
print(' (through getter) ');
return 2*$this->rw_getsetProp;
}
protected function setGetsetProp($value){
print(' (through setter) ');
$this->rw_getsetProp=intval($value/2);
}
public static function yesNo($val){
if($val)
return 'true';
else
return 'false';
}
public static function run(){
// direct new __CLASS__() is not working, so a little trick
$class=__CLASS__;
$pony=new $class();
print("<h1>Test regular properties</h1>\n");
print("Test existance of regular property (true): ".self::yesno(isset($pony->regularProp))."<br>\n");
print("Get value of regular property (1) $pony->regularProp<br>\n");
$pony->regularProp=1024;
print("Set value of regular property (1024) $pony->regularProp<br>\n");
print("<h1>Test readonly properties</h1>\n");
print("Test existance of readonly property (true): ".self::yesno(isset($pony->readonlyProp))."<br>\n");
print("Get value of readonly property (12) $pony->readonlyProp<br>\n");
print("Set value for readonly property (emit warning)<br>\n");
$pony->readonlyProp=77;
print("Test, that readonly property was not changed (12) ".$pony->readonlyProp."<br>\n");
print("<h1>Text readwrite properties</h1>\n");
print("Test existance of readwrite property (true): ".self::yesno(isset($pony->readwriteProp))."<br>\n");
print("Get value of readwrite property (44) $pony->readwriteProp<br>\n");
$pony->readwriteProp=123;
print("Set value for readwrite property (123) $pony->readwriteProp<br>\n");
print("<h1>Test getters/setters</h1>\n");
print("Test existance of getset property (true): ".self::yesno(isset($pony->getsetProp))."<br>\n");
print("Get value of getset property (14) $pony->getsetProp<br>\n");
$pony->getsetProp=777;
print("Set value for readwrite property (776) $pony->getsetProp<br>\n");
print("<h1>Testing exceptions</h1>\n");
print("Test existance of non-existant property (false): ".self::yesno(isset($pony->nonexista))."<br>\n");
print("Test access to non-existant property (emit error and breaks execution)<br>\n");
print("Never prints $pony->nonexista<br>\n");
print("This line won't be printed either<br>\n");
}
}
TestClass::run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.