When I was reading the OOP chapter in the Zend PHP Certification study guide, 5.5, I found a question that gave me a shock from its answer. This question is:
class Magic
{
public $a = "A";
protected $b = array( "a" => "A" , "b" => "B" , "c" => "C" );
protected $c = array( 1 , 2 , 3 );
public function __get( $v )
{
echo "$v";
return $this->b[$v];
}
public function __set( $var , $val )
{
echo "$var: $val,";
$this->$var = $val;
}
}
$m = new Magic();
echo $m->a . ", " . $m->b . ", " . $m->c . ", ";
$m->c = "CC";
echo $m->a . ", " . $m->b . ", " . $m->c . ", ";
The output for this code is:
b, c, A, B, C, c: CC, b, c, A, B, C
Why does this code not print a
, and how does it work?
__get
is only invoked for non-existent or invisible properties. In other words, when you write
$obj->prop
if prop
is defined and is visible in the current context, it will be returned "as is", without calling __get
.
Example:
class X {
public $pub = 1;
private $pri = 2;
function __get($v) {
echo "[get $v]\n";
return 42;
}
function test() {
echo $this->foo, "\n"; // __get invoked
echo $this->pri, "\n"; // no __get
echo $this->pub, "\n"; // no __get
}
}
$x = new X;
$x->test();
echo $x->foo, "\n"; // __get invoked
echo $x->pri, "\n"; // __get invoked (property not visible)
echo $x->pub, "\n"; // no __get
This explains why magic->a
doesn't invoke the getter. Now, since you also have the setter defined, magic->c = CC
actually changes the protected member of the class, therefore, when you echo magic->c
later on, this still invokes the getter (due to c
's invisibility), and the getter returns this->b[c]
, not the actual value of this->c
.
Here's your code, slightly rewritten for clarity:
class Magic
{
public $a = "publicA";
protected $values = array( "a" => "valA" , "b" => "valB" , "c" => "valC" );
protected $c = "oldC";
public function __get( $v )
{
echo "[get $v]\n";
return $this->values[$v];
}
public function __set( $var , $val )
{
echo "[set $var=$val]\n";
$this->$var = $val;
}
}
$m = new Magic();
echo $m->a . ", " . $m->b . ", " . $m->c . "\n";
$m->c = "newC";
echo $m->a . ", " . $m->b . ", " . $m->c . "\n";
Results:
[get b]
[get c]
publicA, valB, valC # no getter for `a`
[set c=newC]
[get b]
[get c] # getter still invoked for `c`
publicA, valB, valC # no getter for `a`
Why is the output different if you replace dots .
in the echo statements with commas:
$m = new Magic();
echo $m->a , ", " , $m->b , ", " , $m->c , "\n";
$m->c = "newC";
echo $m->a . ", " , $m->b , ", " , $m->c , "\n";
Actually, here's what gets printed (demo):
bcA, B, C, c: CC,bcA, B, C,
The point is, each part of $m->a . ", " . $m->b . ", " . $m->c . ", "
expression is evaluated BEFORE the result will be printed by echo
. In this case, there's a side effect of such evaluation.
As @georg demonstrated, that would have been rather different if the code were written as echo $m->a, ', ', $m->b, ', ', $m->c, ', '
instead: in that case, each operand would have been sent to output immediately after its evaluation!
$m->a
part is easy to evaluate, as a
is a public property; its value is string 'A'
.
$m->b
part, however, is a tricky one: b
is a protected property, hence magic method __get()
is to be called. This method prints b
(the name of the property accessed) and returns B
(value of $this->b['b']
). That's why you see your line starting with b
- it's printed before the whole expression is evaluated!
The same happens with $m->c
, as c
is also a protected property: the getter itsels prints c
, but returns C
(value of $this->b['c']
), which will be printed as part of the whole expression.
After that $m->c = "CC"
line is processed, calling another - setter - magic method (as c
is protected, remember). This method is what prints c: CC,
, as $var
is equal to the name of the property to be set, and $val
is its new value (passed into __set
).
But even though magic setter eventually changes the value of c
property, the next line in the code will output the same string as before. It's because the magic getter never access $this->c
- it returns the value of $this->b['c']
instead, when queried for c
.
The bottom line is: also the certification guides and various similar-flavoured tests love magic methods, in the real code it's better avoid them unless you really know what you're going to get. ) Usually these things are hidden deep within the frameworks' cores, serving as engines for high-level abstraction; it's very rare that you need to provide yet another level of high-level abstraction over that one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With