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]
";
return 42;
}
function test() {
echo $this->foo, "
"; // __get invoked
echo $this->pri, "
"; // no __get
echo $this->pub, "
"; // no __get
}
}
$x = new X;
$x->test();
echo $x->foo, "
"; // __get invoked
echo $x->pri, "
"; // __get invoked (property not visible)
echo $x->pub, "
"; // 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]
";
return $this->values[$v];
}
public function __set( $var , $val )
{
echo "[set $var=$val]
";
$this->$var = $val;
}
}
$m = new Magic();
echo $m->a . ", " . $m->b . ", " . $m->c . "
";
$m->c = "newC";
echo $m->a . ", " . $m->b . ", " . $m->c . "
";
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 , "
";
$m->c = "newC";
echo $m->a . ", " , $m->b , ", " , $m->c , "
";
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.
It's because of your __get method in the class. It is always triggering the get method and calling $b
array values.
public function __get( $v )
{
echo "$v";
return $this->b[$v];
}
Every time when you are calling $m->a . ", " . $m->b . ", " . $m->c . ", ";
it's triggering the __get method and displaying the key pair value.
Similarly,
$m->c = "CC";
is triggering the __set method and displaying the set value.
public function __set( $var , $val )
{
echo "$var: $val,";
$this->$var = $val;
}
echo $m->a . ", " . $m->b . ", " . $m->c . ", ";
Now again the __get method is triggered and displaying the key pair value.
The output you got does not match what I got:
bcA, B, C, c: CC,bcA, B, C,
Nevertheless, this class is demonstrating overloading via the magic methods __get()
and __set()
which you may have come across. I think the reason for your confusion may be the order in which the letters came out.
Since the __get()
magic method contains an echo
of the actual property name, you could have gotten abc
had it not been for this small detail:
/** Overloading is not used on declared properties. */
public $a = "A";
Thus, since the expression does not access the magic method for property a
, it will not print the property name, instead, it prints the value. Take note of the order the echo
functions are executed. The echo
in the getter magic method is executed before the echo outside of the class.
Note:
PHP's interpretation of "overloading" is different than most object oriented languages. Overloading traditionally provides the ability to have multiple methods with the same name but different quantities and types of arguments.