我知道这个问题已经被问了好几次,但没有一个对解决方法有真正的答案。也许有一个适合我的具体情况。
我正在构建一个映射器类,该类使用魔术方法__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 手册中可以清楚地看出。对我有用的最简单的解决方法是
假设:
-
$object
是具有重载的对象,__get
和基类__set
,我没有修改的自由。 -
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(( 来解决这个问题。这将返回一个对象,该对象将通过引用默认返回。