PHP-重载属性的间接修改

我知道这个问题已经被问过好几次了,但是他们都没有一个真正的解决方案。也许有一个专门针对我的案子。

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

但是当我试图修改“ roename”的时候:

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

然后它给了我下面的错误:

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

不确定这是否仍然是 PHP 中的一些 bug 或者它是否是“预期行为”,但无论如何它都不能按照我想要的方式工作。这对我来说真是一个精彩的表演... ... 因为我究竟如何才能改变这些延迟加载对象的属性呢? ?


编辑:

实际问题似乎只有在返回包含多个对象的数组时才会出现。

我已经添加了一段示例代码来重现这个问题:

Http://codepad.org/t1ipzm9t

你真的应该在你的 PHP 环境中运行这个,真的看到’错误’。但是这里发生了一些非常有趣的事情。

我尝试改变一个对象的属性,这会给我一个“不能改变重载属性”的通知。但是如果我重复这个属性之后,我看到它确实改变了值... 真的很奇怪..。

160602 次浏览

我也遇到过同样的错误,如果没有完整的代码,就很难确切地指出如何修复它,但这是由于没有 _ _ set 函数造成的。

过去我是这样做的:

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

现在如果你这样做:

echo $user->role->rolename;

你应该看到’测试’

这是由于 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";

很高兴你能给我点东西玩玩

快跑

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

所有你需要做的就是在你的 _ _ get 函数前面加上“ &”作为参考:

public function &__get ( $index )

这个案子纠结了一阵子。

我遇到了与 w00相同的问题,但是我没有自由重写出现这个问题的组件(E _ NOTICE)的基本功能。我已经能够用 数组对象代替基本类型 Array ()来解决这个问题。这将返回一个对象,该对象将通过引用返回缺陷。

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

我也遇到过类似的情况。对于那些不介意取消或重置变量的人来说,最简单的解决办法就是这样做。我非常肯定,从其他答案和 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 和 return & $something,这两个调用不会工作,但是当它们以这种方式工作时,它们从来不调用 _ _ set,而总是调用 _ _ get。

这就是为什么我决定返回一个引用

return &$something;

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

return $something;

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

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