68

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

我正在构建一个映射器类,它使用魔法方法__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对象没有名为“角色”的属性,因此它使用魔术__get()方法创建该对象,并从“角色”对象返回其属性。

但是当我尝试修改“角色名”时:

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

然后它给了我以下错误:

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

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


编辑:

只有当我返回一个包含多个对象的数组时,才会出现实际问题。

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

http://codepad.org/T1iPZm9t

你真的应该在你的 PHP 环境中运行它才能真正看到“错误”。但是这里发生了一些非常有趣的事情。

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

4

8 回答 8

103

您需要做的就是在 __get 函数前面添加“&”以将其作为参考传递:

public function &__get ( $index )

与这个斗争了一段时间。

于 2012-11-01T22:47:33.697 回答
15

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

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 ;
    }   
}
于 2012-05-04T19:48:14.767 回答
10

我也遇到过同样的错误,如果没有您的整个代码,很难准确指出如何修复它,但这是由于没有 __set 函数造成的。

我过去解决它的方法是我做了这样的事情:

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

现在如果你这样做:

echo $user->role->rolename;

你应该看到“测试”

于 2012-05-04T19:29:33.353 回答
4

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

我曾经遇到过类似的情况。对于那些不介意取消设置和重置变量的人来说,最简单的解决方法就是这样做。我很确定从其他答案和 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方法以返回一个引用,而不是昂贵的大数组复制。

于 2013-11-01T15:23:54.817 回答
3

我收到了这样的通知:

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

这修复了它:

$tmp = $myClass->my_magic_property;
$var = reset($tmp);
于 2017-04-11T15:53:45.480 回答
3

我同意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 函数时。

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

于 2019-05-30T12:03:14.260 回答
2

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

于 2013-06-11T10:28:02.467 回答
1

这是由于 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";
于 2012-05-04T19:42:54.293 回答