ArrayAccess в PHP – назначение смещения по ссылке

Во-первых, цитата из руководства ole 'на ArrayAccess::offsetSet() :

Эта функция не вызывается в назначениях по ссылке и иным образом косвенным изменениям в размерах массива, перегруженных с помощью ArrayAccess (косвенные в том смысле, что они сделаны не путем изменения измерения напрямую, а путем изменения подразмера или суб-свойства или назначения размера массива по ссылке на другую переменную). Вместо этого вызывается ArrayAccess :: offsetGet () . Операция будет успешной только в том случае, если этот метод возвращается по ссылке, что возможно только с PHP 5.3.4 .

Я немного смущен этим. По-видимому, это говорит о том, что ( по 5.3.4 ) можно определить offsetGet() для возврата по ссылке в классе реализации, таким образом обрабатывая назначения по ссылке.

Итак, теперь тестовый фрагмент:

( Игнорируйте отсутствие проверки и проверки isset() )

 class Test implements ArrayAccess { protected $data = array(); public function &offsetGet($key) { return $this->data[$key]; } public function offsetSet($key, $value) { $this->data[$key] = $value; } public function offsetExists($key) { /* ... */ } public function offsetUnset($key) { /* ... */ } } $test = new Test(); $test['foo'] = 'bar'; $test['foo'] = &$bar; // Fatal error: Cannot assign by reference to // overloaded object in var_dump($test, $bar); 

Хорошо, так что это не сработает. Тогда на что относится это руководство?

причина
Я хотел бы разрешить назначение по ссылке через оператор массива объекту, реализующему ArrayAccess , как показывает пример. Я исследовал это раньше, и я не думал, что это возможно, но, вернувшись к этому из-за неопределенности, я ( повторно ) обнаружил это упоминание в руководстве. Теперь я просто смущен.


Обновление . Когда я нажал « Опубликовать свой вопрос» , я понял, что это скорее всего относится к присваиванию ссылкой на другую переменную, например $bar = &$test['foo']; , Если это так, то извиняйтесь; хотя зная, как, если это вообще возможно, назначить по ссылке на перегруженный объект, было бы здорово.


Дальнейшая разработка : к чему все это относится, я хотел бы иметь следующие псевдонимы методов:

 isset($obj[$key]); // $obj->has_data($key); $value = $obj[$key]; // $obj->get_data($key); $obj[$key] = $value; // $obj->set_data($key, $value); $obj[$key] = &$variable; // $obj->bind_data($key, $variable); // also, flipping the operands is a syntactic alternative $variable = &$obj[$key]; // $obj->bind_data($key, $variable); unset($obj[$key]); // $obj->remove_data($key); 

Поскольку has , get , set и remove go, они не являются проблемой с поддерживаемыми методами ArrayAccess . Функциональность привязки – это то место, где я нахожусь в убытке, и начинаю понимать, что ограничения ArrayAccess и PHP просто запрещают это.

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

 class Test implements ArrayAccess{ protected $_data = array(); public function &offsetGet($key){ return $this->_data[$key]; } ... public function offsetSetReference($key, &$value) { $this->_data[$key] = &$value; } } $test = new Test(); $test['foo'] = $var = 'bar'; $test->offsetSetReference('bar', $var); $var = 'foo'; echo $test['bar']; # foo $alias = &$test['bar']; $alias = 'hello :)'; echo $var; # hello :) 

Вероятно, такая функция была забыта, когда ArrayAccess был впервые реализован.

Изменить: передать его как «ссылочное задание»:

 class ArrayAccessReferenceAssignment { private $reference; public function __construct(&$reference) { $this->reference = &$reference; } public function &getReference() { $reference = &$this->reference; return $reference; } } class Test implements ArrayAccess{ ... public function offsetSet($key, $value){ if ($value instanceof ArrayAccessReferenceAssignment) { $this->offsetSetReference($key, $value->getReference()); } else { $this->_data[$key] = $value; } } 

Что тогда работает безупречно, потому что вы внедрили его. Это, вероятно, более приятное взаимодействие, чем более явный вариант offsetSetReference выше:

 $test = new Test(); $test['foo'] = $var = 'bar'; $test['bar'] = new ArrayAccessReferenceAssignment($var); $var = 'foo'; echo $test['bar']; # foo $alias = &$test['bar']; $alias = 'hello :)'; echo $var; # hello :) 

То, о чем говорится в руководстве, – это так называемые «косвенные модификации». Рассмотрим следующий сценарий:

 $array = new ArrayObject; $array['foo'] = array(); $array['foo']['bar'] = 'foobar'; 

В приведенном выше скрипте $array['foo'] = array(); будет вызывать offsetSet('foo', array()) . $array['foo']['bar'] = 'foobar'; с другой стороны, вызовет offsetGet('foo') . Почему так? Последняя строка будет оцениваться примерно так же под капотом:

 $tmp =& $array['foo']; $tmp['bar'] = 'foobar'; 

Таким образом, $array['foo'] сначала извлекается ref, а затем изменяется. Если ваш offsetGet возвращается с помощью ref, это будет успешным. Если нет, вы получите некоторую косвенную ошибку модификации.


То, что вы хотите, с другой стороны, – это полная противоположность: не извлекать значение по ссылке, а назначать его. Для этого теоретически потребуется подпись offsetSet($key, &$value) , но практически это невозможно .

Кстати, ссылки трудно понять. Вы получите много неочевидного поведения, и это особенно верно для ссылок на элементы массива (у них есть специальные правила). Я бы рекомендовал вам просто избежать их вообще.