ArrayAccess в PHP - назначение смещения по ссылке - PullRequest
16 голосов
/ 31 декабря 2011

Во-первых, цитата из руководства 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, какПример фрагмента показывает.Я исследовал это раньше, и я не думал, что это было возможно, но вернувшись к этому из-за неопределенности, я ( re-) обнаружил это упоминание в руководстве.Теперь я просто в замешательстве.


Обновление : Когда я нажал Опубликовать ваш вопрос , я понял, что это скорее всего относится к заданиюпо ссылке на другую переменную , например $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 просто запрещают это.

Ответы [ 2 ]

9 голосов
/ 31 декабря 2011

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

$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 возвращается по ссылке, это будет успешно.Если нет, вы получите некоторую косвенную ошибку модификации.


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

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

4 голосов
/ 31 декабря 2011

Это не работает с 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 :)
...