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
не имеет свойства, называемого "роль", поэтому он использует магический метод __get()
для создания этого объекта и возвращает его свойство из объекта "role".
Но когда я пытаюсь изменить "rolename":
$user = createUser();
$user->role->rolename = 'Test';
Тогда это дает мне следующую ошибку:
Примечание: косвенная модификация перегруженного свойства не имеет эффекта
Не уверен, что это все еще некоторая ошибка в PHP или если это "ожидаемое поведение", но в любом случае это не работает так, как я хочу. Это действительно шоу-стоппер для меня... Потому что, насколько я могу изменить свойства ленивых загруженных объектов?
EDIT:
Фактическая проблема возникает только при возврате массива, который содержит несколько объектов.
Я добавил пример кода, который воспроизводит проблему:
http://codepad.org/T1iPZm9t
Вы действительно должны запускать это в своей среде PHP, действительно видите "ошибку". Но здесь есть что-то действительно интересное.
Я пытаюсь изменить свойство объекта, которое дает мне уведомление "can not change overloaded property". Но если я повторю свойство после этого, я вижу, что он действительно DID меняет значение... Действительно странно...
Ответы
Ответ 1
Приятно, что ты дал мне кое-что, чтобы поиграть с
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;
Выход
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
Все, что вам нужно сделать, это добавить "&" перед вашей функцией __get передать ее в качестве ссылки:
public function &__get ( $index )
Повлиял с этим на некоторое время.
Ответ 3
У меня была такая же ошибка, без вашего всего кода трудно точно определить, как ее исправить, но это вызвано отсутствием функции __set.
То, что я получил в прошлом, это то, что я сделал такие вещи:
$user = createUser();
$role = $user->role;
$role->rolename = 'Test';
теперь, если вы это сделаете:
echo $user->role->rolename;
вы должны увидеть "Тест"
Ответ 4
Хотя я очень опаздываю в этом обсуждении, я думал, что это может быть полезно для кого-то в будущем.
Я столкнулся с подобной ситуацией. Самое простое решение для тех, кто не против сброса и сброса переменной, - это сделать это. Я уверен, что причина, по которой это не работает, понятна из других ответов и из руководства 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
, чтобы вернуть ссылочное довольно дорогое копирование больших массивов.
Ответ 5
Я получил это уведомление для этого:
$var = reset($myClass->my_magic_property);
Это зафиксировало это:
$tmp = $myClass->my_magic_property;
$var = reset($tmp);
Ответ 6
Это происходит из-за того, как 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";
Ответ 7
Я столкнулся с той же проблемой, что и w00, но у меня не было возможности переписать базовую функциональность компонента, в котором возникла эта проблема (E_NOTICE). Я смог исправить проблему, используя ArrayObject вместо базового типа массив(). Это вернет объект, который по умолчанию будет возвращен ссылкой.