PHP - 间接修改重载属性


PHP - Indirect modification of overloaded property

我知道这个问题已经被问了好几次,但没有一个对解决方法有真正的答案。也许有一个适合我的具体情况。

我正在构建一个映射器类,该类使用魔术方法__get()延迟加载其他对象。它看起来像这样:

public function __get ( $index )
{
    if ( isset ($this->vars[$index]) )
    {
        return $this->vars[$index];
    }
    // $index = 'role';
    $obj = $this->createNewObject ( $index );
    return $obj;
}

在我的代码中,我做到了:

$user = createObject('user');
$user->role->rolename;

到目前为止,这有效。User对象没有名为"role"的属性,因此它使用魔术__get()方法来创建该对象,并从"role"对象返回其属性。

但是当我尝试修改"角色名称"时:

$user = createUser();
$user->role->rolename = 'Test';

然后它给了我以下错误:

注意:间接修改超载属性无效

不确定这是否仍然是 PHP 中的一些错误,或者它是否是"预期行为",但无论如何它都不能按照我想要的方式工作。这对我来说真的是一个表演障碍...因为我到底如何能够更改延迟加载对象的属性?


编辑:

实际问题似乎仅在我返回包含多个对象的数组时才发生。

我添加了一个重现该问题的示例代码段:

http://codepad.org/T1iPZm9t

您应该真正在您的

PHP环境中运行它,真正看到"错误"。但是这里发生了一些非常有趣的事情。

尝试更改对象的属性,这给了我"无法更改重载属性"的通知。但是,如果我在那之后回显该属性,我看到它实际上确实更改了值......真的很奇怪...

需要做的就是在__get函数前面添加"&"以将其作为引用传递:

public function &__get ( $index )

为此挣扎了一段时间。

很好,

你给了我一些可以玩的东西

class Sample extends Creator {
}
$a = new Sample ();
$a->role->rolename = 'test';
echo  $a->role->rolename , PHP_EOL;
$a->role->rolename->am->love->php = 'w00';
echo  $a->role->rolename  , PHP_EOL;
echo  $a->role->rolename->am->love->php   , PHP_EOL;

输出

test
test
w00

使用的类

abstract class Creator {
    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }
        return $this->{$name};
    }
    public function __set($name, $value) {
        $this->{$name} = new Value ( $name, $value );
    }

}
class Value extends Creator {
    private $name;
    private $value;
    function __construct($name, $value) {
        $this->name = $name;
        $this->value = $value;
    }
    function __toString()
    {
        return (string) $this->value ;
    }
}      

编辑:根据要求新建阵列支持

class Sample extends Creator {
}
$a = new Sample ();
$a->role = array (
        "A",
        "B",
        "C" 
);

$a->role[0]->nice = "OK" ;
print ($a->role[0]->nice  . PHP_EOL);
$a->role[1]->nice->ok = array("foo","bar","die");
print ($a->role[1]->nice->ok[2]  . PHP_EOL);

$a->role[2]->nice->raw = new stdClass();
$a->role[2]->nice->raw->name = "baba" ;
print ($a->role[2]->nice->raw->name. PHP_EOL);

输出

 Ok die baba

修改后的类

abstract class Creator {
    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }
        return $this->{$name};
    }
    public function __set($name, $value) {
        if (is_array ( $value )) {
            array_walk ( $value, function (&$item, $key) {
                $item = new Value ( $key, $item );
            } );
        }
        $this->{$name} = $value;
    }
}
class Value {
    private $name ;
    function __construct($name, $value) {
        $this->{$name} = $value;
        $this->name = $value ;
    }
    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }
        if ($name == $this->name) {
            return $this->value;
        }
        return $this->{$name};
    }
    public function __set($name, $value) {
        if (is_array ( $value )) {
            array_walk ( $value, function (&$item, $key) {
                $item = new Value ( $key, $item );
            } );
        }
        $this->{$name} = $value;
    }
    public function __toString() {
        return (string) $this->name ;
    }   
}

我遇到了同样的错误,如果没有您的整个代码,很难准确确定如何修复它,但这是由没有__set函数引起的。

过去绕过它的方式是我做过这样的事情:

$user = createUser();
$role = $user->role;
$role->rolename = 'Test';

现在,如果您这样做:

echo $user->role->rolename;

您应该看到"测试">

虽然我在这个讨论中很晚,但我认为这对将来的某个人可能会有用。

我也面临过类似的情况。对于那些不介意取消设置和重置变量的人来说,最简单的解决方法是这样做。我很确定这不起作用的原因从其他答案和 php.net 手册中可以清楚地看出。对我有用的最简单的解决方法是

假设:

  1. $object是具有重载的对象,__get和基类__set,我没有修改的自由。
  2. shippingData是我想修改字段的数组,例如:- phone_number

 

// First store the array in a local variable.
$tempShippingData = $object->shippingData;
unset($object->shippingData);
$tempShippingData['phone_number'] = '888-666-0000' // what ever the value you want to set
$object->shippingData = $tempShippingData; // this will again call the __set and set the array variable
unset($tempShippingData);

注意:此解决方案是解决问题并复制变量的快速解决方法之一。如果数组太庞大,最好强制重写 __get 方法以返回引用,而不是昂贵的大数组副本。

我收到此通知:

$var = reset($myClass->my_magic_property);

这修复了它:

$tmp = $myClass->my_magic_property;
$var = reset($tmp);

我同意 VinnyD 的观点,您需要做的是在__get函数前面添加"&",以使其返回所需的结果作为引用:

public function &__get ( $propertyname )

但请注意两件事:

1(你也应该做

return &$something;

或者您可能仍在返回值而不是引用...

2(请记住,在任何情况下,__get返回引用,这也意味着永远不会调用相应的__set;这是因为 PHP 通过使用 __get 返回的引用来解决此问题,该引用被调用!

所以:

$var = $object->NonExistentArrayProperty; 

表示调用 __get,并且由于__get具有 &__get 并返回 &$something,因此$var现在按预期是对重载属性的引用......

$object->NonExistentArrayProperty = array(); 

按预期工作,__set按预期调用...

但:

$object->NonExistentArrayProperty[] = $value;

$object->NonExistentArrayProperty["index"] = $value;

预期工作,即元素将在重载数组属性中正确添加或修改,但不会调用__set:将改为调用__get!

如果不使用 &__get 并返回 &$something,这两个调用将不起作用,但是当它们以这种方式工作时,它们从不调用__set,而是始终调用__get。

这就是我决定返回引用的原因

return &$something;

当$something是一个 array(( 时,或者当重载属性没有特殊的 setter 方法时,而是返回一个值

return $something;

当$something不是数组或具有特殊的 setter 函数时。

无论如何,这对我来说很难正确理解! :)

这是因为 PHP 如何处理重载属性,因为它们不可修改或通过引用传递。

有关重载的更多信息,请参阅手册。

若要变通解决此问题,您可以使用__set函数或创建createObject方法。

下面是一个__get__set,它提供了与您类似的情况的解决方法,您只需修改__set即可满足您的需求。

请注意,__get实际上永远不会返回变量。 相反,一旦您在对象中设置了一个变量,它就不再重载了。

/**
 * Get a variable in the event.
 *
 * @param  mixed  $key  Variable name.
 *
 * @return  mixed|null
 */
public function __get($key)
{
    throw new 'LogicException(sprintf(
        "Call to undefined event property %s",
        $key
    ));
}
/**
 * Set a variable in the event.
 *
 * @param  string  $key  Name of variable
 *
 * @param  mixed  $value  Value to variable
 *
 * @return  boolean  True
 */
public function __set($key, $value)
{
    if (stripos($key, '_') === 0 && isset($this->$key)) {
        throw new 'LogicException(sprintf(
            "%s is a read-only event property", 
            $key
        ));
    }
    $this->$key = $value;
    return true;
}

这将允许:

$object = new obj();
$object->a = array();
$object->a[] = "b";
$object->v = new obj();
$object->v->a = "b";

遇到了与w00相同的问题,但我没有自由重写发生此问题(E_NOTICE(的组件的基本功能。我已经能够使用 ArrayObject 而不是基本类型 array(( 来解决这个问题。这将返回一个对象,该对象将通过引用默认返回。