PHP - 间接修改重载属性

时间:2012-05-04 19:10:36

标签: php magic-methods

我知道这个问题已被问过几次,但他们都没有一个真正的解决办法。也许有一个针对我的具体案例。

我正在构建一个mapper类,它使用魔术方法__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'的属性,因此它使用magic __get()方法创建该对象,并从'role'对象返回其属性。

但是当我尝试修改'rolename'时:

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

然后它给了我以下错误:

  

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

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


编辑:

当我返回包含多个对象的数组时,似乎只会发生实际问题。

我添加了一段代码来重现问题:

http://codepad.org/T1iPZm9t

你应该在你的PHP环境中真正运行它,真正看到'错误'。但是这里有一些非常有趣的东西。

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

8 个答案:

答案 0 :(得分:89)

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

public function &__get ( $index )

暂时挣扎这个。

答案 1 :(得分: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 ;
    }   
}

答案 2 :(得分:9)

我遇到了同样的错误,如果没有完整的代码,很难确切地指出如何修复它,但这是由于没有__set函数引起的。

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

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

现在如果你这样做:

echo $user->role->rolename;

你应该看到'测试'

答案 3 :(得分:3)

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

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

假设:

  1. $object是来自基类的重载__get__set的对象,我无法自由修改。
  2. shippingData是我要修改的字段数据,例如: - phone_number
  3. // 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方法以返回引用相当昂贵的大数组副本可能是件好事。

答案 4 :(得分:3)

我收到此通知是为了这样做:

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

这解决了它:

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

答案 5 :(得分:1)

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

有关重载的详细信息,请参阅manual

要解决此问题,您可以使用__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";

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

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