我知道这个问题已被问过几次,但他们都没有一个真正的解决办法。也许有一个针对我的具体案例。
我正在构建一个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中的一些错误,或者它是否是“预期的行为”,但无论如何它都不能按照我想要的方式运行。这对我来说真的是一个阻止...因为我怎么能改变延迟加载对象的属性?
编辑:
当我返回包含多个对象的数组时,似乎只会发生实际问题。
我添加了一段代码来重现问题:
你应该在你的PHP环境中真正运行它,真正看到'错误'。但是这里有一些非常有趣的东西。
我尝试更改对象的属性,这给了我“无法更改重载属性”的通知。但如果我在之后回复该属性,我发现它实际上是DID改变了值......真的很奇怪......
答案 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手册中可以清楚地看出来。最简单的解决方法是
假设:
$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
方法以返回引用相当昂贵的大数组副本可能是件好事。
答案 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函数时。
无论如何,这对我来说很难理解! :)