简体   繁体   English

为什么在尝试分配对属性 object 的引用时调用 __get() 魔术方法?

[英]Why is the __get() magic method called when trying to assign a reference to a property object?

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 https://onlinephp.io/c/f7f16

here you can see, I create an stdClass object, try to pass it to a non-exists variable, and __get() gets executed - even though I was just tryting to write it, instead of reading.在这里你可以看到,我创建了一个stdClass object,尝试将它传递给一个不存在的变量,然后__get()被执行——即使我只是想写它,而不是阅读。 And if I didn't die() it, I get Indirect modification of overloaded property has no effect exception.如果我没有die()它,我会得到Indirect modification of overloaded property has no effect异常。

If I have this (no reference)如果我有这个(没有参考)

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 https://onlinephp.io/c/3afef

everything works fine, but why?一切正常,但为什么呢?

Internally, PHP has a concept of 'reading' a property in order to perform a write/modify operation.在内部,PHP 具有“读取”属性以执行写入/修改操作的概念。 Though it may seem incoherent at first glance, this allows PHP to perform certain in-place modification operations with less overhead, that would otherwise have to rely on read-modify-write round-trips.虽然乍一看似乎不连贯,但这允许 PHP 以更少的开销执行某些就地修改操作,否则必须依赖读-修改-写往返。 Situations where it is used include appending to an array and creating a new reference to the property.使用它的情况包括附加到数组和创建对该属性的新引用。 For this to work with dynamically-resolved properties, the __get magic method must return a reference, which is to say, a place which can have its value modified;为了使用动态解析的属性, __get魔术方法必须返回一个引用,也就是说,一个可以修改其值的地方; otherwise a notice will be triggered with the message 'Indirect modification of overloaded property has no effect'.否则将触发一条通知,消息为“间接修改重载属性无效”。

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);

You will get this message in the above example if you remove the & from the declaration of function& __get , which will make the method return a value, and not a reference.如果您从function& __get的声明中删除& ,您将在上面的示例中收到此消息,这将使该方法返回一个值,而不是一个引用。

The same code path of 'reading-in-order-to-modify' is triggered when an object property is on the left-hand side of reference-assignment ( =& ).当 object 属性位于引用分配 ( =& ) 的左侧时,会触发相同的代码路径“reading-in-order-to-modify”。 But of course, if property lookup is performed dynamically by user code (what PHP misnames 'overloading'), there is no way PHP can promise that 'after $obj->prop =& (expr) is executed, accessing $obj->prop will resolve to the same thing (expr) did at that time', so PHP throws an exception.但是,当然,如果属性查找是由用户代码动态执行的(PHP 错误命名为“重载”),则 PHP 无法在$obj->prop =& (expr)执行后访问$obj->prop将解析为当时(expr)所做的相同事情',因此 PHP 会抛出异常。 However, the interpreter realises this only after the __get magic method has already been invoked.然而,解释器只有在__get魔术方法已经被调用后才意识到这一点。

This can be seen by inspecting the source code of the Zend engine.这可以通过检查 Zend 引擎的源代码看出。 To evaluate an expression like $obj->prop =& expr , the Zend engine calls the function zend_assign_to_property_reference .为了评估像$obj->prop =& expr这样的表达式,Zend 引擎调用 function zend_assign_to_property_reference This function invokes zend_fetch_property_address in order to determine which zval (an internal representation of a PHP value) to modify so that it becomes an alias for the same zval as the right-hand side expression.此 function 调用zend_fetch_property_address以确定要修改哪个 zval(PHP 值的内部表示),以便它成为与右侧表达式相同的 zval 的别名。 It does so by invoking the get_property_ptr_ptr internal method slot of the object; 通过调用 object 的get_property_ptr_ptr内部方法槽来实现; if that returns nothing, there is a fallback to the read_property slot.如果没有返回任何内容,则会回read_property插槽。 For user-code objects, this means invoking the zend_std_read_property function, which is where the call to the __get magic method is actually made .对于用户代码对象,这意味着调用zend_std_read_property function,这是实际调用__get魔法方法的地方

Again, because zend_std_read_property is called with flags that signify it is being 'read-in-order-to-modify', if __get happens not to return a reference (because it is not declared function& __get ), an E_NOTICE error is raised.同样,因为zend_std_read_property时带有表示它正在“按顺序读取以修改”的标志,如果__get碰巧没有返回引用(因为它没有声明为function& __get ),则会引发E_NOTICE错误。 But either way, zend_std_read_property will return its result in the zval prepared for it by zend_fetch_property_address instead of a pre-existing one, which causes the latter not to wrap it in a so-called indirect zval as zend_assign_to_property_reference expects, which causes that function in turn to throw an exception.但无论哪种方式, zend_std_read_property都会在zend_fetch_property_address为它准备的 zval 中返回其结果,而不是预先存在的 zval,这导致后者不会zend_assign_to_property_reference期望的那样将其包装在所谓的间接 zval中,这又导致 function抛出异常。

This is a pretty baroque implementation, if you ask me.如果您问我,这是一个非常巴洛克式的实现。 I even agree that the Zend engine could have been implemented so that by the point __get is about to be called, the interpreter realises it's actually pointless to invoke and throws an exception immediately.我什至同意可以实现 Zend 引擎,以便在即将调用__get时,解释器意识到调用实际上毫无意义并立即抛出异常。 But given that this code path is hit only in user code that is incorrect and should be rewritten, perhaps it makes little sense to optimise.但鉴于此代码路径仅在不正确且应重写的用户代码中命中,因此优化可能意义不大。

If you compiled the list of things in PHP that make no sense, I'm not sure this would even make the top ten;如果你在 PHP 中列出了一些毫无意义的东西,我不确定这是否会进入前十; the language is just that bizarre.语言就是那么奇怪。 Get used to it.习惯它。 Or don't: changing the language is also an option.或者不要:更改语言也是一种选择。

When you assign by reference 1 , the left hand variable needs to be defined (it exists) and if it doesn't (it is undefined), PHP will define it.当您通过引用1进行分配时,需要定义左侧变量(存在),如果没有定义(未定义),则 PHP 将定义它。

However not in the $t->prop2 case:但不是在$t->prop2的情况下:

The variable's member name ( $t->prop2 ) is undefined but the __get() 2 magic method of $t dictates PHP to not define it as it would normally do, but instead pass on to __get() as first parameter the members' name, to obtain its overloaded value.变量的成员名称 ( $t->prop2 ) 是未定义的,但是$t__get() 2魔术方法指示 PHP 不要像通常那样定义它,而是传递给__get()作为成员的第一个参数名称,以获取其重载值。

This is flawed, as to assign by reference requires the variable (or property) to be defined for a successful operation.这是有缺陷的,因为通过引用分配需要定义变量(或属性)才能成功操作。 But undefined (overloaded) can not fulfill this.但是未定义(重载)不能实现这一点。

Therefore the warning:因此警告:

Indirect modification of overloaded property stdClass@anonymous::$prop2 has no effect间接修改重载属性 stdClass@anonymous::$prop2 没有效果

and finally the fatal error:最后是致命错误:

Cannot assign by reference to overloaded object无法通过引用分配给重载 object

Example Code 3,4示例代码3,4

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

$t->prop2 = &$x1;

  1. Assignment by Reference - PHP Manual 按参考分配 - PHP 手册
  2. Property overloading - PHP Manual 属性重载 - PHP 手册
  3. Example code on 3v4l.org 3v4l.org 上的示例代码
  4. Backwards compatible example code on 3v4l.org for historic view 3v4l.org 上用于历史视图的向后兼容示例代码

There are a lot of complex explanations here is a simple answer as to why it's calling __get() ,这里有很多复杂的解释是关于为什么调用__get()的简单答案,

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

Take the simple example above, to be able to set $obj->var to the memory address that $test points to, $obj->var needs a memory address inside $obj, without __get PHP will just create one on its own per the code above if your using __get PHP won't and needs you to define it.以上面的简单示例为例,为了能够将$obj->var设置为$test指向的 memory 地址,$obj->var 需要 $obj 中的一个 memory 地址,如果没有__get PHP,则只会自行创建一个如果您使用__get PHP 则上面的代码不会并且需要您定义它。 the only place you could do that definition is inside that __get你唯一可以做这个定义的地方是在__get

Per documentation : 根据文档

__get() is utilized for reading data from inaccessible (protected or private) or non-existing properties. __get()用于从不可访问(受保护或私有)或不存在的属性中读取数据。

if you want to use __get by reference, here what you should write:如果您想通过引用使用__get ,请在此处编写:

public function &__get ( $index )

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么更新存储为私有属性的对象的属性时,PHP __set()魔术方法无法启动? - Why doesn't the PHP __set() magic method fire when updating a property of an object stored as a private property? 使用魔术__get方法时禁止显示不确定的属性警告 - Suppress unidentified property warning when using magic __get method PHP-错误:尝试为购物车数组元素分配变量时,“试图获取非对象的属性” - PHP - error: 'Trying to get property of non-object' when trying to assign variables with shopping cart array elements 调用现有方法时执行(魔术)方法 - execute (magic) method when existing method is called 从被调用函数访问时尝试获取非对象的属性 - Trying to get property of non-object when accessing from called function 将对象转换为数组 - 调用任何魔术方法? - Casting object to array - any magic method being called? Laravel错误:通过first()方法访问时尝试获取非对象的属性 - Laravel Error: Trying to get property of non-object when accessing via first() method PHP-如何知道魔术方法__call()从何处调用 - PHP - How to know where magic method __call() get called from PHP中的Magic Method用于设置声明的属性时的触发方法? - Magic Method in PHP for triggering method when a declared property is set? Magento中是否有一种魔术方法,当用户离开页面时会自动调用该魔术方法? - Is there a magic method in Magento that will be called automatically when a user leaves a page?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM