@MaxPyane

Куда утрачивается ссылка на параметр в методе?

Имею не мало опыта работы с PHP, но в этот раз напоролся на не очевидный для меня подводный камень с ссылками который никогда до этого не встречал, может баг, а может я туплю. В общем простой метод, который играет роль заполнителя внешнего хранилища $storage (простая переменная-объект). Проблема возникает когда я внутри метода обращаюсь к сторонней библиотеке (ReflectionDocBlock), она мне парсит док коммент откуда я в свою очередь выдергиваю некоторые ха-ки и добавляю в $storage. Иногда она не может спарсить (например плохо сделанный док коммент), и именно в этот момент происходят какие-то аномалии хотя ни ошибок, ни исключений она не кидает.

private function parseClassAttrsIn(object &$storage, object $source)
{
    $storage->name = 'test';
    // Смотрим что внутри
    var_dump($storage); // object(classStorage)#2513 (1) { ["name"]=> string(4) "test" }

    // Вызываем метод который просто обращается к инстансу ReflectionDocBlock,
    // ничего в нем магического нет
    $docBlock = $this->reflectionDocBlock($source->getDocComment());

    // Опять смотрим что внутри
    var_dump($storage); // string(20) "object(classStorage)"
}

Что здесь произошло o_O? Единственное что осталось от моего $storage'a это описательная стринга, мол здесь был объект classStorage...
Я попробовал работать без ссылки и возвращать $storage, проблема уходит, но это не вариант.
private function parseAttrsIn(object $source, object $storage)
{
    $storage->name = 'test';
    // [...]
    return $storage;
}

Этот $storage удобно передавать ссылкой другим методам и заполнять его ха-ками. Я знаю что PHP очень умный и оптимизированный внутри, и параметры, даже если там обычная переменная, все равно передается по ссылке, и при обращении к нему внутри функции/метода мы все так же работаем с ссылкой, но как только мы задумали его изменить, то он копируется и превращается в локальную переменную. Без амперсанда я теряю $storage, т.к внутри метода изменяю его динамически объявляя свойства. Как я понимаю, амперсандом я говорю PHP в лоб не копировать, а продолжать работать через ссылку.
Понять что за аномалия там происходит я не могу, достаточно перестать обращаться к внешней библиотеке (закомментировать) и ссылки не ломаются. У кого какие идеи?
  • Вопрос задан
  • 117 просмотров
Пригласить эксперта
Ответы на вопрос 2
SilenceOfWinter
@SilenceOfWinter
та еще зажигалка...
передавать объекты по ссылке как минимум не имеет смысла т.к. объект переданный в метом все равно указывает на тот же объект и отзязать его можно только через клонирование.
Ответ написан
prototype_denis
@prototype_denis
Symfony
<?php

declare(strict_types=1);

class A {
    public $property = 'foo';
}
class B {
    public $property = 'foo';
}

$a = new A();
$b = new B();

function byIdentifier($object) {
    $object->property = 'bar';
    $object = null;
}

function byReference(&$object) {
    $object->property = 'bar';
    $object = null;
}

byIdentifier($a); 
byReference($b);

var_dump($a); // class A#2 { public $property => string(3) "bar" }
var_dump($b); // NULL


Это ни разу не аномалия...

Вас сбивает с толку слово объект и фраза что они всегда передаются по ссылке.

И да и нет. Объекты действительно всегда передаются по ссылке, но в метод летит не объект, а "алиас" на значение. С амперсандом кол-во "алиасов" на данные не измениться и в методе вы можете переопределить, как значение, так и этот "алиас"

Другой пример

<?php

declare(strict_types=1);

$array = ['foo'];

foreach ($array as &$item) {
    $item = $array;
}
$item = 'string';

\var_dump($array);


$array останется массивом, но если написать `$item = &$array;` то станет строкой.

Тут дело в том, что в первом случае у нас две копии "алиасов" на данные, а во втором один. И изменяя после foreach $item - мы изменяем копию. Ну а если прировнять по ссылке, то изменим данные и массив станет строкой.

Я попробовал работать без ссылки и возвращать $storage, проблема уходит, но это не вариант.


...и далее сами себе противоречите

В вашем случае амперсанд не нужен - ну вот вообще не нужен, потому что вы работаете с объектом и меняете объект. У вас не массив или скаляр, где он, возможно был бы необходим. Поймите, передавая что-то с амперсандом вы влияете не только на значение, но и на количество ссылок на данные в PHP позволяя налету менять не данные, а сам "алиас"

Алиас - более верное название для указателя в PHP

Всё это описано тут https://www.php.net/manual/ru/language.references.php - просто нужно немного разобраться самому )))
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы