当我阅读Zend PHP认证学习指南5.5中的OOP章节时,我发现了一个让我对它的答案感到震惊的问题.这个问题是:
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 . ", ";
Run Code Online (Sandbox Code Playgroud)
此代码的输出是:
b, c, A, B, C, c: CC, b, c, A, B, C
Run Code Online (Sandbox Code Playgroud)
为什么这段代码不能打印a
,它是如何工作的?
geo*_*org 10
__get
仅针对不存在或不可见的属性调用.换句话说,当你写作
$obj->prop
Run Code Online (Sandbox Code Playgroud)
if prop
已定义且在当前上下文中可见,它将"按原样"返回,而不调用__get
.
例:
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
Run Code Online (Sandbox Code Playgroud)
这解释了为什么magic->a
不调用getter.现在,由于你也定义了setter,magic->c = CC
实际上改变了类的受保护成员,因此,当你magic->c
稍后回调时,这仍然会调用getter(由于c
不可见),并且getter返回this->b[c]
,而不是实际值this->c
.
这是您的代码,为了清晰起见,稍作重写:
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";
Run Code Online (Sandbox Code Playgroud)
结果:
[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`
Run Code Online (Sandbox Code Playgroud)
如果.
用逗号替换echo语句中的点,为什么输出会有所不同:
$m = new Magic();
echo $m->a , ", " , $m->b , ", " , $m->c , "\n";
$m->c = "newC";
echo $m->a . ", " , $m->b , ", " , $m->c , "\n";
Run Code Online (Sandbox Code Playgroud)
实际上,这是打印的内容(演示):
bcA, B, C, c: CC,bcA, B, C,
Run Code Online (Sandbox Code Playgroud)
关键是,$m->a . ", " . $m->b . ", " . $m->c . ", "
表达式的每个部分都会在结果打印之前进行评估echo
.在这种情况下,这种评估会产生副作用.
正如@georg所展示的那样,如果代码被编写为echo $m->a, ', ', $m->b, ', ', $m->c, ', '
反而会有所不同:在这种情况下,每个操作数在评估后都会立即发送到输出!
$m->a
部分很容易评估,a
公共财产也是如此; 它的值是字符串'A'
.
$m->b
但是,部分是一个棘手的问题:b
是受保护的属性,因此__get()
要调用魔法.此方法打印b
(访问的属性的名称)并返回B
(值$this->b['b']
).这就是为什么你看到你的行开始b
- 它是在整个表达式被评估之前打印的!
同样的情况也会发生$m->c
,因为c
它也是一个受保护的属性:getter itsels打印c
,但返回C
(value of $this->b['c']
),它将作为整个表达式的一部分打印出来.
$m->c = "CC"
处理完该行后,调用另一个 - setter - 魔术方法(c
保护,记住).这种方法就是打印c: CC,
,因为$var
等于要设置的属性的名称,并$val
为它的新值(传入__set
).
但即使魔术设置器最终改变了c
属性值,代码中的下一行也将输出与之前相同的字符串.这是因为神奇的getter永远不会访问$this->c
- 它会$this->b['c']
在查询时返回相反的值c
.
底线是:认证指南和各种类似风味的测试都喜欢魔术方法,在真实的代码中,除非你真的知道自己会得到什么,否则最好避免它们.通常这些东西隐藏在框架的核心内部,作为高级抽象的引擎; 你很少需要提供另一个级别的高级抽象.