魔术吸气剂/定型器未被调用

Abu*_*ouq 9 php oop echo

当我阅读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)


rai*_*7ow 6

实际上,这是打印的内容(演示):

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.


底线是:认证指南和各种类似风味的测试都喜欢魔术方法,在真实的代码中,除非你真的知道自己会得到什么,否则最好避免它们.通常这些东西隐藏在框架的核心内部,作为高级抽象的引擎; 你很少需要提供另一个级别的高级抽象.