PHP currently supports property overloading provided by magic methods __get() and __set(). Those methods are called when accessed property is not declared in the class. Actually this overloading is a bit frustrating.
This is my solution how to simulate properties with accessor methods in a Delphi way.
So…
// define new "like-keyword"
// value is short and unusual string
define('property', "\0\0");
Maybe _property
is better choice. It doesn't matter now.
And the Initializator:
function _property($obj)
{
// use cache (analyze object only once)
static $cache;
$props = & $cache[ get_class($obj) ];
if ($props === null) {
// build list of properties
// 1) get_object_vars prevents ObjectIteration
// 2) outside of objects returns only public members
$props = array_flip(array_keys(get_object_vars($obj), property, true));
}
foreach ($props as $name => $foo) {
unset($obj->$name); // unset property to pass control to __set() and __get()
}
return $props;
}
See documented source code of class:
class Rectangle
{
// list of properties
private $_props;
// shadow properties
private $_width;
private $_height;
/**
* @var int phpDoc for virtual property!
* getter: $_width setter: setWidth
*/
public $width = property;
public $height = property;
public $area = property;
function __construct()
{
// property initialization
$this->_props = _property($this);
}
/**
* Universal setter.
*
* If property 'abc' is declared,
* there must be setAbc() setter,
* otherwise property is read-only
*/
final function __set($name, $value)
{
if (!isset($this->_props[$name])) {
throw new Exception("Undefined property '$name'.");
} elseif (method_exists($this, 'set'.$name)) {
$this->{'set'.$name}($value);
} else {
throw new Exception("Property '$name' is read-only.");
}
}
/**
* Universal getter.
*
* If property 'abc' is declared,
* there must be getAbc() getter,
* otherwise variable $_abc
*/
final function &__get($name)
{
if (!isset($this->_props[$name])) {
throw new Exception("Undefined property '$name'.");
} elseif (method_exists($this, 'get'.$name)) {
return $this->{'get'.$name}();
} else {
return $this->{'_'.$name};
}
}
// getters and setters:
protected function setWidth($value)
{
$this->_width = abs($value);
}
protected function setHeight($value)
{
$this->_height = abs($value);
}
protected function getArea()
{
return $this->_height * $this->_width;
}
}
// usage:
$rect = new Rectangle();
$rect->width = 10;
$rect->height = 5;
echo $rect->area;
// prints 50
// try receive private member
echo $rect->props;
// throw exception
Pros:
- universal __set & __get, no overriden is needed
- magic methods know if specific property exists
- so this mechanism cannot be misused to get private members
- properties are case-sensitive, like other variables
- these __set & __get works very fast (no switches or ifs)
- virtual properties may be documented with any of the existing tools (phpDoc, doxygen)
- IDE autocomplete based on phpDoc will work
- easy adaptive for future PHP6 (I hope)
Cons:
- overloading is still not safe (see bug #36484, fixed in PHP 5.2.1)
Since PHP 5.1.0 it is also possible to overload the isset() and unset():
final private function __isset($name)
{
return isset($this->_props[$name]);
}
final private function __unset($name)
{
unset($this->_props[$name]);
}
Some links:
Comments
Milf #1
Já výše uvedenému nerozumím, tak se zeptám – k čemu je uvedené dobré?
Předem děkuji za vysvětlení. Z teorie mám něco vyčteno a praxe zdá se mi býti poněkud odlišná, ztrácím se v problematice.
p.s. snad nevadí, že se nedržím angličtiny, bylo by to asi veselejší
Milf #2
Ok, question in English comes now 😛
Manual says about setters/getters …
My question extends previous question (Czech one) – when and why it happens, what is wrong if members don't exist and someone is trying to access them?
I have no idea, I am thinking about not so good analysis.
David Grudl #3
#1 Milf, Zkus si přečíst třeba tohle https://www.sitepoint.com/community/ nebo https://www.sitepoint.com/community/
Milf #4
#3 David Grudl, ok, díky za materiál k samostudiu 😉 Z toho co jsem zatím přečetl (6 stran) se mi nejvíce zamlouvá tato myšlenka:
😛
llook #5
I have totaly refused to use
__get
and__set
magics. You have to take more care, than with POJO-like getters and setters. I think that$this->foo++
requires more imagination than$this->setFoo($this->getFoo() + 1)
.For example,
$this->foo++
modifies the property, but$this->foo[ 1] = 'bar'
doesn't (as seen in your previous article). It could sometimes be a bit confusing.So only one overloading function that I am currently using is
__call
, the code is similar to this:I can use Java-like setters and getters without the amount of methods with the same code (which is so typical for Java-like [gs]etters in Java) and I am always sure if I am calling the setter.
And when I realy need to encapsulate a field, I just write proper get/set methods (a setter throwing exception for readonly fields) with no need of API modification.
Before time I was also using
__get/__set
in this way:But then I had found that I am only using get? and set?, so I have dropped those two methods out.
Co je to za novou módu tyhlety dvojjazyčný weblogy?
llook #6
Mám návrh na vylepšení – odkazy na ostatní komentáře nějak odlišit už v náhledu.
hvge #7
Offtopic: Tie vase priklady v PHP 5 ma motivovali pridat chybajuce klucove slova do FSHL :)
David Grudl #8
#5 llook, Komentovat můžeš klidně česky. Já jsem k tomuto zveřejněnému postupu otevřel diskuzi na SitePoint a proto ta angličtina.
Jinak přesně ty “arrays via getters” jsou to hlavní, co mě stále od jejich použití odrazuje. Tohle musím ještě domyslet…
David Grudl #9
#5 llook, how encapsulate an array:
or simply with ampersand:
GgzeE #10
Už to asi není aktuální, ale pošlu sem mojí úpravu kódu v článku pro případné zájemce.
Vysvětlení příkladu:
Takže první obsah souboru vlastnosti.php (třída Vlastnosti):
Obsah souboru testovaciTrida.php (ukázka testovací třídy za využití třídy Vlastnosti):
A konečně obsah index.php (testování testovací třídy 🙂:
Tak snad Vám můj kód bude k užitku…
Štěpán Svoboda #11
#9 David Grudl, V podstatě tam ty public properties jsou jenom proto aby šli dokumentovat phpdocem a fungovalo ide autocomplete. Nebo mají ještě nějaký jiný význam?
Štěpán Svoboda #12
#11 Štěpán Svoboda, teda ne že by tyhle dvě věci nestačili 😉
Štěpán Svoboda #13
Ještě tady nikdo nezmínil možnost, že by Property byl objekt. Už dlouho o tom přemýšlím a nezdá se mi to tak úplně ztracené. Taková Vlastnost by potom mohla mít třeba masku nebo rozsah hodnot…
Ano, dá se to řešit setterem, ale to se mi právě moc nelíbí. Objektově zpracované události by mohli těžit z možností jež nám nabízí dědění…
Štěpán Svoboda #14
offtopic: jestli tenhle komentář už bude barevnej tak jsem se na nic zeptat nechtěl ;) a jestli ne tak jsem se chtěl zeptat jak udělam barevnej php kód
Wouter #15
The use of getters and setters (in C#) is to CONTROL the way properties (class variables) are used by instances. NOT to look good! So the getter is simply to return the value of the property and the setters is to control the setting of the value of the property. If for some reason you use this to mimic the C# getters & setters, you loose all benefit: CONTROL OVER VALUE OF PROPERTIES. I don't understand why PHP would call this getters and setters, it's just not the same.
This article has been closed. It is no longer possible to add comments.