php 为什么在试图为属性对象赋值引用时调用__get()魔术方法?

iqjalb3h  于 2023-01-08  发布在  PHP
关注(0)|答案(4)|浏览(125)
class Test
{
    public $prop1;
    public function __get(string $n)
    {
        echo '__GET: '.$n.' - but why?<br>';
        die;
    }
}

$t = new Test();
$x1 = new \stdClass();
$t->prop2 = &$x1;
echo '.';
die;

https://onlinephp.io/c/f7f16
在这里,您可以看到,我创建了一个stdClass对象,尝试将其传递给一个不存在的变量,然后__get()被执行-即使我只是尝试写入它,而不是阅读它。如果我不执行die(),我会得到Indirect modification of overloaded property has no effect异常。
如果我有这个(无参考)

class Test
{
    public $prop1;
    public function __get(string $n)
    {
        echo '__GET: '.$n.' - but why?<br>';
        die;
    }
}

$t = new Test();
$x1 = new \stdClass();
$t->prop2 = $x1;
echo '.';
die;

https://onlinephp.io/c/3afef
一切正常,但为什么

nzrxty8p

nzrxty8p1#

在内部,PHP有一个“阅读”属性的概念来执行写/修改操作。虽然乍看之下似乎不连贯,但这允许PHP以更少的开销执行某些就地修改操作。否则必须依赖于读取-修改-写入的往返过程。使用它的情况包括追加到数组和创建对属性的新引用。为了使它能够与动态解析的属性一起工作,__get魔术方法必须返回一个引用,也就是说,一个可以修改其值的位置;否则将触发一个通知,并显示消息“间接修改重载属性无效”。

class Globalses {
    public function& __get(string $name) {
        echo "{$name} GET!\n";
        return $GLOBALS[$name];
    }
}

$obj = new Globalses;

$foo = [0, 1, 2, 3];
$obj->foo[] = 4;     // foo GET!
echo 'foo='; debug_zval_dump($foo);

$bar = 42;
$var =& $obj->bar;   // bar GET!
$var = 69;
echo 'bar='; debug_zval_dump($bar);

如果从function& __get的声明中删除&,则会在上面的示例中得到此消息,这将使该方法返回值,而不是引用。
当对象属性位于引用赋值的 * 左侧 * 时,将触发相同的代码路径“reading-in-order-to-modify当然,如果属性查找是由用户代码动态执行的(PHP错误地命名了“overloading”),PHP无法保证“在执行$obj->prop =& (expr)之后,访问$obj->prop将解析为与(expr)当时所做的相同的事情',因此PHP抛出异常。然而,解释器只有在调用了__get魔术方法之后才意识到这一点。
通过查看Zend引擎的源代码可以看出这一点。要计算$obj->prop =& expr这样的表达式,Zend引擎调用函数zend_assign_to_property_reference。此函数调用zend_fetch_property_address以确定哪个zval(PHP值的内部表示),以便它成为右侧表达式的同一个zval的别名。它通过调用对象的get_property_ptr_ptr内部方法槽来实现;如果没有返回任何值,则返回到read_property插槽,对于用户代码对象,这意味着调用zend_std_read_property函数,这是实际调用__get魔术方法的地方。
同样,由于zend_std_read_property是用标志调用的,这些标志表示它正在“按顺序读入以修改”,因此如果__get碰巧没有返回引用(因为它没有被声明为function& __get),则会引发E_NOTICE错误。但是无论哪种方式,zend_std_read_property都将其结果返回到由zend_fetch_property_address为其准备的zval中,而不是预先存在的zval中,这导致后者 not 将其 Package 在所谓的间接Zval中,如zend_assign_to_property_reference所期望的,这导致该函数进而抛出异常。
如果你问我的话,这是一个相当巴洛克式的实现。我甚至同意Zend引擎可以这样实现,即在__get即将被调用时,解释器意识到调用它实际上是毫无意义的,并立即抛出异常。但考虑到只有在不正确的用户代码中才会遇到这个代码路径,应该重写,也许优化它没有什么意义。
如果你用PHP列出了一些毫无意义的东西,我不确定这是否能排在前十位;语言就是这么奇怪。习惯它。或者不习惯:改变语言也是一种选择。

brqmpdu1

brqmpdu12#

当你通过reference 1赋值时,左手变量需要被定义(它存在),如果没有(它未定义),PHP将定义它。
但在$t->prop2情况下则不同:
变量的成员名($t->prop2)是未定义的,但是$t__get() 2魔术方法指示PHP不要像通常那样定义它,而是将成员名作为第一个参数传递给__get(),以获得其重载值。
这是有缺陷的,因为通过引用赋值 * 需要 * 定义变量(或属性)才能成功操作,但undefined(重载)不能满足这一要求。
因此,警告:
重载属性stdClass@anonymous::$prop2的间接修改无效
最后是致命错误:
无法按引用分配给重载对象

示例代码3,4

$t = new class() extends stdClass
{
    public function __get(string $name) {
        $x = null;
        return $x;
    }
};

$t->prop2 = &$x1;

1.按引用分配- PHP手册
1.属性重载- PHP Manual

  1. Example code on 3v4l.org
  2. Backwards compatible example code on 3v4l.org for historic view
rta7y2nd

rta7y2nd3#

这里有很多复杂的解释,这里有一个简单的答案,为什么它调用__get()

$obj = new \stdClass();
$test = "hello";
$obj->var = &%test;

以上面的简单示例为例,为了能够将$obj->var设置为$test所指向的内存地址,$obj-〉var需要$obj中的内存地址,如果没有__get,PHP将根据上面的代码自行创建一个内存地址,如果您使用的__get PHP不需要并且需要您定义它的话。您可以进行定义的唯一位置是在__get

wbgh16ku

wbgh16ku4#

根据文件:
__get()用于从不可访问(受保护或私有)或不存在的属性读取数据。
如果您想通过引用使用__get,下面是您应该编写内容:

public function &__get ( $index )

相关问题