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
, как показывает пример. Я исследовал это раньше, и я не думал, что это возможно, но, вернувшись к этому из-за неопределенности, я (повторно) обнаружил это упоминание в руководстве. Теперь я просто смущен.
Обновить. Когда я нажал Post Your Question, я понял, что это скорее всего относится к присваиванию ссылочной переменной другой, например $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 просто запрещают это.
Ответы
Ответ 1
Это не работает с 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 :)
Ответ 2
К чему относится руководство, относятся так называемые "косвенные модификации". Рассмотрим следующий script:
$array = new ArrayObject;
$array['foo'] = array();
$array['foo']['bar'] = 'foobar';
В приведенном выше script $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)
, но практически это просто невозможно.
Кстати, ссылки трудно понять. Вы получите много неочевидного поведения, и это особенно верно для ссылок на элементы массива (у них есть специальные правила). Я бы рекомендовал вам просто избежать их вообще.