bounty将在21小时后过期。回答此问题可获得+400的声望奖励。John Smith正在寻找来自知名来源的答案。
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
一切都很好,但为什么呢?
4条答案
按热度按时间esyap4oy1#
在内部,PHP有一个“阅读”属性的概念,以执行写/修改操作。虽然乍看起来似乎不连贯,但它允许PHP以更少的开销执行某些就地修改操作,否则必须依赖于读取-修改-写入的往返。使用它的情况包括追加到数组和创建对该属性的新引用。为了使其与动态解析的属性一起工作,
__get
魔术方法必须返回一个引用,也就是说,一个可以修改其值的位置;否则将触发一个通知,并显示消息“间接修改重载属性无效”。如果从
function& __get
的声明中删除&
,则在上面的示例中将出现此消息。当对象属性位于引用赋值的 * 左侧 * 时,将触发相同的“reading-in-order-to-modify”代码路径(
=&
)。当然,如果属性查找是由用户代码动态执行的,(PHP错误地命名为“overloading”),PHP无法保证“在执行$obj->prop =& (expr)
之后,访问$obj->prop
将解析为与(expr)
当时所做的相同的事情',因此PHP抛出一个异常。然而,解释器只有在已经调用了__get
魔术方法之后才意识到这一点。这可以通过检查Zend引擎的源代码看到。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中没有意义的东西,我不确定这是否能排在前十位;语言就是这么奇怪。习惯它。或者不要:改变语言也是一种选择。
tzdcorbm2#
当你通过reference 1赋值时,左手变量需要被定义(它存在),如果它不存在(它是未定义的),PHP将定义它。
但在
$t->prop2
情况下则不然:变量的成员名称(
$t->prop2
)是未定义的,但是$t
的__get()
2魔术方法指示PHP不像通常那样定义它,而是将成员名称作为第一个参数传递给__get()
,以获得其重载值。这是有缺陷的,因为通过引用赋值 * 需要 * 定义变量(或属性)才能成功操作。但未定义(重载)不能满足这一要求。
因此警告:
重载属性stdClass@anonymous::$prop2的间接修改无效
最后是致命错误:
无法按引用分配给重载对象
示例代码3,4
1.按引用赋值- PHP手册
1.属性重载- PHP Manual
x9ybnkn63#
有很多复杂的解释,这里有一个简单的答案,为什么它调用
__get()
,以上面的简单例子为例,为了能够将
$obj->var
设置为$test
指向的内存地址,$obj-〉var需要$obj中的内存地址,如果没有__get
,PHP将根据上面的代码自己创建一个内存地址,如果使用__get
的PHP不需要并且需要您定义它。您唯一可以做该定义的地方是在__get
中vlju58qv4#
根据文件:
__get()
用于从不可访问的(受保护的或私有的)或不存在的属性阅读数据。如果您想通过引用方式使用
__get
,则应编写以下内容: