Подтвердить что ты не робот

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 меняет значение... Действительно странно...

4b9b3361

Ответ 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 вместо базового типа массив(). Это вернет объект, который по умолчанию будет возвращен ссылкой.