PHP - 重载属性的间接修改
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10454779/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
PHP - Indirect modification of overloaded property
提问by w00
I know this question has been asked several times, but none of them have a real answer for a workaround. Maybe there's one for my specific case.
我知道这个问题已经被问过好几次了,但他们都没有真正的解决方法。也许我的具体情况有一个。
I'm building a mapper class which uses the magic method __get()to lazy load other objects. It looks something like this:
我正在构建一个映射器类,它使用魔法方法__get()来延迟加载其他对象。它看起来像这样:
public function __get ( $index )
{
if ( isset ($this->vars[$index]) )
{
return $this->vars[$index];
}
// $index = 'role';
$obj = $this->createNewObject ( $index );
return $obj;
}
In my code I do:
在我的代码中,我这样做:
$user = createObject('user');
$user->role->rolename;
This works so far. The Userobject doesn't have a property called 'role', so it uses the magic __get()method to create that object and it returns its property from the 'role' object.
到目前为止,这有效。该User对象没有名为“role”的属性,因此它使用魔术__get()方法创建该对象,并从“role”对象返回其属性。
But when i try to modify the 'rolename':
但是当我尝试修改“角色名”时:
$user = createUser();
$user->role->rolename = 'Test';
Then it gives me the following error:
然后它给了我以下错误:
Notice: Indirect modification of overloaded property has no effect
注意:间接修改重载属性没有效果
Not sure if this is still some bug in PHP or if it's "expected behaviour", but in any case it doesn't work the way I want. This is really a show stopper for me... Because how on earth am I able to change the properties of the lazy loaded objects??
不确定这是否仍然是 PHP 中的一些错误,或者它是否是“预期行为”,但无论如何它都无法按我想要的方式工作。这对我来说真的是一个表演障碍......因为我到底怎么能改变懒加载对象的属性??
EDIT:
编辑:
The actual problem only seems to occur when I return an array which contains multiple objects.
实际问题似乎只在我返回包含多个对象的数组时发生。
I've added an example piece of code which reproduces the problem:
我添加了一段重现问题的示例代码:
You should really run this in your PHP environment the really see the 'error'. But there is something really interesting going on here.
你真的应该在你的 PHP 环境中运行它,真正看到“错误”。但是这里发生了一些非常有趣的事情。
I try to change the property of an object, which gives me the notice 'cant change overloaded property'. But if I echo the property after that I see that it actually DID change the value... Really weird...
我尝试更改对象的属性,这给了我“无法更改重载属性”的通知。但是,如果我在此之后回显该属性,我会看到它实际上确实改变了值......真的很奇怪......
采纳答案by Baba
Nice you gave me something to play around with
很高兴你给了我玩的东西
Run
跑
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;
Output
输出
test
test
w00
Class Used
使用的类
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 ;
}
}
Edit : New Array Support as requested
编辑:按要求提供新阵列支持
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);
Output
输出
Ok die baba
Modified Class
修改类
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 ;
}
}
回答by VinnyD
All you need to do is add "&" in front of your __get function to pass it as reference:
您需要做的就是在 __get 函数前添加“&”以将其作为参考传递:
public function &__get ( $index )
Struggled with this one for a while.
与这个斗争了一段时间。
回答by Phil W
I've had this same error, without your whole code it is difficult to pinpoint exactly how to fix it but it is caused by not having a __set function.
我也遇到过同样的错误,如果没有你的整个代码,很难准确指出如何修复它,但它是由没有 __set 函数引起的。
The way that I have gotten around it in the past is I have done things like this:
我过去解决它的方式是我做了这样的事情:
$user = createUser();
$role = $user->role;
$role->rolename = 'Test';
now if you do this:
现在如果你这样做:
echo $user->role->rolename;
you should see 'Test'
你应该看到“测试”
回答by Althaf M
Though I am very late in this discussion, I thought this may be useful for some one in future.
虽然我在这次讨论中已经很晚了,但我认为这可能对将来的某些人有用。
I had faced similar situation. The easiest workaround for those who doesn't mind unsetting and resetting the variable is to do so. I am pretty sure the reason why this is not working is clear from the other answers and from the php.net manual. The simplest workaround worked for me is
我也遇到过类似的情况。对于那些不介意取消和重置变量的人来说,最简单的解决方法是这样做。我很确定这不起作用的原因从其他答案和 php.net 手册中很清楚。对我有用的最简单的解决方法是
Assumption:
假设:
$objectis the object with overloaded__getand__setfrom the base class, which I am not in the freedom to modify.shippingDatais the array I want to modify a field of for e.g. :- phone_number
$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);
Note: this solution is one of the quick workaround possible to solve the problem and get the variable copied. If the array is too humungous, it may be good to force rewrite the __getmethod to return a reference rather expensive copying of big arrays.
注意:此解决方案是解决问题并复制变量的一种快速解决方法。如果数组太庞大,强制重写__get方法以返回引用相当昂贵的大数组复制可能会很好。
回答by Andrew
I was receiving this notice for doing this:
我因为这样做而收到此通知:
$var = reset($myClass->my_magic_property);
This fixed it:
这修复了它:
$tmp = $myClass->my_magic_property;
$var = reset($tmp);
回答by Shores
I agree with VinnyDthat what you need to do is add "&" in front of your __get function, as to make it to return the needed result as a reference:
我同意VinnyD 的观点,您需要做的是在 __get 函数前添加“&”,以使其返回所需的结果作为参考:
public function &__get ( $propertyname )
But be aware of two things:
但要注意两点:
1) You should also do
1)你也应该做
return &$something;
or you might still be returning a value and not a reference...
或者你可能仍然返回一个值而不是一个引用......
2) Remember that in any case that __get returns a reference this also means that the corresponding __set will NEVER be called; this is because php resolves this by using the reference returned by __get, which is called instead!
2)记住,在任何情况下 __get 返回一个引用,这也意味着相应的 __set 永远不会被调用;这是因为 php 使用 __get 返回的引用来解决这个问题,而是调用它!
So:
所以:
$var = $object->NonExistentArrayProperty;
means __get is called and, since __get has &__get and return &$something, $var is now, as intended, a reference to the overloaded property...
意味着 __get 被调用,并且由于 __get 有 &__get 并返回 &$something,$var 现在正如预期的那样,是对重载属性的引用......
$object->NonExistentArrayProperty = array();
works as expected and __set is called as expected...
按预期工作,__set 按预期调用...
But:
但:
$object->NonExistentArrayProperty[] = $value;
or
或者
$object->NonExistentArrayProperty["index"] = $value;
works as expected in the sense that the element will be correctly added or modified in the overloaded array property, BUT __set WILL NOT BE CALLED: __get will be called instead!
在元素将在重载的数组属性中正确添加或修改的意义上按预期工作,但 __set 不会被调用: __get 将被调用!
These two calls would NOT work if not using &__get and return &$something, but while they do work in this way, they NEVER call __set, but always call __get.
如果不使用 &__get 并返回 &$something,这两个调用将不起作用,但是虽然它们以这种方式工作,但它们从不调用 __set,而是始终调用 __get。
This is why I decided to return a reference
这就是我决定返回参考的原因
return &$something;
when $something is an array(), or when the overloaded property has no special setter method, and instead return a value
当 $something 是一个数组(),或者当重载的属性没有特殊的 setter 方法时,而是返回一个值
return $something;
when $something is NOT an array or has a special setter function.
当 $something 不是数组或具有特殊的 setter 函数时。
In any case, this was quite tricky to understand properly for me! :)
无论如何,这对我来说很难正确理解!:)
回答by Klaas van der Weij
I have run into the same problem as w00, but I didn't had the freedom to rewrite the base functionality of the component in which this problem (E_NOTICE) occured. I've been able to fix the issue using an ArrayObjectin stead of the basic type array(). This will return an object, which will defaulty be returned by reference.
我遇到了与 w00 相同的问题,但我没有自由重写发生此问题 (E_NOTICE) 的组件的基本功能。我已经能够使用ArrayObject而不是基本类型array()来解决这个问题。这将返回一个对象,该对象将默认通过引用返回。
回答by Nick
This is occurring due to how PHP treats overloaded properties in that they are not modifiable or passed by reference.
这是由于 PHP 处理重载属性的方式造成的,因为它们不可修改或通过引用传递。
See the manualfor more information regarding overloading.
有关过载的更多信息,请参阅手册。
To work around this problem you can either use a __setfunction or create a createObjectmethod.
要解决此问题,您可以使用__set函数或创建createObject方法。
Below is a __getand __setthat provides a workaround to a similar situation to yours, you can simply modify the __setto suite your needs.
下面是一个__get与__set您类似情况的解决方法,您可以简单地修改它__set以满足您的需求。
Note the __getnever actually returns a variable. and rather once you have set a variable in your object it no longer is overloaded.
注意__getnever 实际上返回一个变量。而一旦你在你的对象中设置了一个变量,它就不再重载了。
/**
* 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;
}
Which will allow for:
这将允许:
$object = new obj();
$object->a = array();
$object->a[] = "b";
$object->v = new obj();
$object->v->a = "b";

