Reputation: 4296
I was studying magic PHP's methods and came up with very strange behavior. My code:
<?php
class Magic
{
public $a = "A";
protected $b = ["a" => "A", "b" => "B", "c" => "C"];
protected $c = [1, 2, 3];
public function __get($v)
{
echo "$v,";
return $this->b[$v];
}
}
$m = new Magic();
echo $m->a.",";
echo $m->b.",";
echo $m->c;
echo PHP_EOL;
echo $m->a.",".$m->b.",".$m->c;
Output:
A,b,B,c,C
b,c,A,B,C
I was hoping to get something like this:
A,b,B,,c,,C
A,b,B,,c,,C
Why output lines are different?
Upvotes: 2
Views: 116
Reputation: 3107
I've edited the magic method like this, added internal to the output.
public function __get($v)
{
echo " internal $v,";
return $this->b[$v];
}
And i got
A, internal b,B, internal c,C
internal b, internal c,A,B,C
So, when we call $m->a
it returns the a
property because it's defined public
ly.
$b and $c are protected, which means our magic method is going to work.
echo $m->a.",";
Says A,
, without invoking the magic __get method. Because a is a public property.
While echo $m->b.",";
says internal b, B,
because it invokes our magic method.
We gave the variables one by one, but in the second call, we want all three at once.
In this case, when we give all three variables to echo
command at once; it first tries and gets all three of them, and then print them on the screen. (see Jon's comment.)
While the echo command is collecting its information, it invokes our magic __get method twice for b and c and our method prints something on the screen. (internal b, internal c,
) Then, after our magic methods returned and echo command collected the info it needed, it prints what it's got, which is A,B,C
Upvotes: 3
Reputation: 2323
That is actually expected behavior.
When accessing virtual property like b
or c
your code first echos its name and then returns its value which is then echoed.
echo $m->b.',';
is same as
echo "b,"; // echo "$v," from __get(b)
echo "B" . ","; // echo return value from __get(b)
Same happens after echoing PHP_EOL. It first processes all echo's "arguments" and thus echoing all variable names, then returning values, concatenating and echoing them.
echo $m->a.",".$m->b.",".$m->c;
is same as
echo "b,"; // echo "$v," from __get(b)
echo "c,"; // echo "$v," from __get(c)
echo "A" . ","; // echo value of public property a
echo "B" . ","; // echo return value from __get(b)
echo "C"; // echo return value from __get(c)
Upvotes: 2
Reputation: 437386
The first line of output should not be surprising:
echo $m->a.","; // $m->a is accessible, prints "A,"
echo $m->b.","; // not accessible, __get called, prints "b,", ret, prints "B,"
echo $m->c; // not accessible, __get called, prints "c,", ret, prints "C"
The final result should be A,b,B,c,C
as you observed.
The second line is surprising at first sight, but not that much if you think about it. It has to do with the order of evaluation of the sub-expressions in $m->a.",".$m->b.",".$m->c
.
As is evident from the result, what PHP does is first evaluate all three property reads in order of appearance (which immediately causes the side effects b,
and c,
to appear) and only then concatenates the results. This produces A,B,C
, which is appended.
PHP does not guarantee any particular order of evaluation for sub-expressions, either in this or in any other situation. This means that you have to be very careful if you have expressions that cause side effects, as these side effects may be observed to happen in a counter-intuitive order (you have already found out).
Exploratory exercises aside, a good rule of thumb to follow in PHP (and also in any other imperative language) is to not write code that produces a result together with side effects (which is really a special case of the single responsibility principle).
Upvotes: 1
Reputation: 50041
When you do: echo $m->a.",".$m->b.",".$m->c;
, you get b,c,A,B,C
, because the echo statements by __get
(in lowercase) are executed before the $m->a.",".$m->b.",".$m->c
string is concatenated and therefore before any of it (A,B,C
) is echoed.
In the other case, you echo after each __get call, so the output (A,b,B,c,C
) has the values intermingled.
Upvotes: 2