oop — ссылки на PHP не работают так, как я ожидаю

В основном, как я понимаю, ссылки работают

$a = 5;
$b = &$a;
$a = 10;
echo $b; // 10;

Однако в этом фрагменте кода я получаю неожиданный (для меня, который, вероятно, есть объяснение) результат

class Room {

private $users = array();

public function addUser(&$user){
$this->users[] = $user;
}
}

$users = array(
1 => 'Tom',
2 => 'Hank',
3 => 'Sam',
4 => 'John'
);

$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);

unset($users[3]);

echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";

Я ожидаю, после сброса $users[3]Единственный пользователь внутри $room быть Tom, но это не так, оба Tom а также Sam присутствуют в объекте. Почему unset не влияет на свойство объекта?

Даже если я сделаю шаг вперед с примером и создам класс User эффект все тот же

class Room {

private $users = array();

public function addUser(&$user){
$this->users[] = $user;
}
}

class User {

public $name;

function __construct($name){
$this->name = $name;
}
}

$users = array(
1 => new User('Tom'),
2 => new User('Hank'),
3 => new User('Sam'),
4 => new User('John')
);

$room = new Room();
$room->addUser($users[1]);
$room->addUser($users[3]);

unset($users[3]);

echo "<pre>" . print_r($room, true) . "</pre>";
echo "<pre>" . print_r($users, true) . "</pre>";

2

Решение

Основы подсчета ссылок :

Переменная PHP хранится в контейнере, называемом «zval». Звал
Контейнер содержит, кроме типа и значения переменной, два
дополнительные биты информации. Первый называется «is_ref» и является
логическое значение, указывающее, является ли переменная частью
«набор ссылок». (…) Поскольку PHP допускает ссылки на пользователя, как
созданный & оператор, контейнер zval также имеет внутренний
механизм подсчета ссылок для оптимизации использования памяти. Этот второй
часть дополнительной информации, называемая «refcount», содержит сколько
имена переменных (также называемые символами) указывают на этот контейнер zval
.

(…)

Переменные контейнеры уничтожаются, когда «refcount» достигает нуля.
«refcount» уменьшается на единицу когда любой символ связан с
переменная-контейнер покидает область видимости (например, когда функция заканчивается) или
когда вызывается unset () на символе.

Пример с массивами:

<?php
$a = array(
0 => 'aaa',
1 => 'bbb',
2 => 'ccc',
);
debug_zval_dump($a);
// ... string(3) "bbb" refcount(1) ...

$b = array();
$b[0] = &$a[0];
$b[1] = &$a[1];

$a[1] = 'ddd';
debug_zval_dump($a);
// ... &string(3) "bbb" refcount(2) ...
debug_zval_dump($b);
// ... &string(3) "bbb" refcount(2) ...

unset($a[1]);
debug_zval_dump($a);
/*
array(2) refcount(2){
[0]=>
&string(3) "aaa" refcount(2)
[1]=>
&string(3) "ddd" refcount(2)
}
*/
debug_zval_dump($b);
// ... string(3) "ddd" refcount(1) ...

var_dump($a);
/*
array (size=2)
0 => &string 'aaa' (length=3)
2 => string 'ccc' (length=3)
*/
var_dump($b);
/*
array (size=2)
0 => &string 'aaa' (length=3)
1 => string 'ddd' (length=3)
*/
1

Другие решения

Unset работает с символами, а не с эталонными целями.

Вот почему использование unset для неопределенной переменной не вызывает каких-либо ошибок.

$a = 10;
$b = &$a;
unset($b); // forget the name "$b" exists.
echo $a; // 10

Если вы хотите сбросить его в обоих местах, вы должны назначить null к одной из переменных. Это «жесткий сброс», а не «мягкий сброс», которым вы сейчас занимаетесь.

Также вы не назначаете ссылку, вы назначаете копию.

$this->users[] = &$user;
4

Я думаю, что есть небольшая логическая проблема между вашим желаемым эффектом и тем, как вы пытаетесь это сделать.

Если я правильно понимаю, вы хотите назначить пользователей для контейнера, а затем сбросить один из этих пользователей таким образом, чтобы он также был отменен в вашем контейнере. это

unset($users[3]);

сбрасывает значение четвертого элемента вашего массива пользователей.

если бы мы сделали $user[3] = 'foo'; значение, содержащееся в соответствующей записи контейнера, будет установлено на 'foo' также, но сам индексный ключ контейнера не будет сброшен или затронут ссылкой, потому что это не часть ссылочного значения

Если вы хотите удалить пользователя, либо вы отслеживаете, какой индексный ключ назначен тому или иному пользователю в вашем контейнере, а затем удаляете пользователей с этим индексным ключом, либо устанавливаете значение $users[3] в null (или что угодно, что вам нужно) и пропустите null значения при работе с вашим контейнером

0

Быть осторожен.

Вы переходите к addUser() ссылка на строку 'Tom' выделяется при построении массива $users,

Первый, addUser() должен прочесть $this->users[] =& $user;в противном случае вы будете копировать значение в $this->users[] вместо того, чтобы делиться ссылкой.

Теперь оба $users а также Room::$users разделяют одни и те же объекты, однако unset($users[3]) удаляет элемент сопоставляется с индексом 3 из массива, это не разрушает сопоставленный объект.

-1
По вопросам рекламы [email protected]