Свойство многомерного массива только для чтения, PHP

#php #arrays #getter-setter #magic-methods

#php #массивы #средство получения-установки #magic-методы

Вопрос:

Я некоторое время дурачился с ArrayAccess и волшебством PHP ( __get , __set ), и я застрял.

Я пытаюсь реализовать класс, в котором некоторые свойства, представляющие собой массивы, доступны только для чтения. Они будут установлены изначально конструктором, но не должны быть изменены впоследствии.

Используя __get magic by reference, я могу получить доступ к элементам массива произвольно глубоко в свойствах, и я думал, что смогу генерировать исключения, когда эти свойства нацелены через __set .

Проблема, однако, в том, что когда я обращаюсь к значению элемента массива, PHP вызывает __get , чтобы вернуть эту часть массива по ссылке, и я не знаю, является ли это действием чтения или записи.

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

Простой пример:

 class Test{
    public function amp;__get($key){
        echo "[READ:{$key}]n";
    }
    public function __set($key, $value){
        echo "[WRITE:{$key}={$value}]n";
    }
}

$test = new Test;

$test->foo;
$test->foo = 'bar';

$test->foo['bar'];
$test->foo['bar'] = 'zip';
  

И вывод:

 [READ:foo]
[WRITE:foo=bar]
[READ:foo]
[READ:foo] // here's the problem
  

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

Я уже наполовину смирился с тем, что этого невозможно достичь, но я все еще полон надежды. Есть ли у кого-нибудь идеи, как можно сделать то, чего я хочу достичь?

Я рассматривал некоторые возможные обходные пути с ArrayAccess , но, насколько я могу судить, в конечном итоге я вернусь к этому месту, учитывая, что я собираюсь использовать обозначение свойств, которое вызывает __get .


Обновление: Еще один веселый день с ArrayAccess .

(Это другая проблема, но я полагаю, что это работает в. Публикую просто так, для острастки.)

 class Mf_Params implements ArrayAccess{

    private $_key       = null;
    private $_parent    = null;
    private $_data      = array();
    private $_temp      = array();

    public function __construct(Array $data = array(), $key = null, self $parent = null){
        $this->_parent  = $parent;
        $this->_key     = $key;
        foreach($data as $key => $value){
            $this->_data[$key] = is_array($value)
                ? new self($value, $key, $this)
                : $value;
        }
    }

    public function toArray(){
        $array = array();
        foreach($this->_data as $key => $value){
            $array[$key] = $value instanceof self
                ? $value->toArray()
                : $value;
        }
        return $array;
    }

    public function offsetGet($offset){
        if(isset($this->_data[$offset])){
            return $this->_data[$offset];
        }
        // if offset not exist return temp instance
        return $this->_temp[$offset] = new self(array(), $offset, $this);
    }

    public function offsetSet($offset, $value){
        $child = $this;
        // copy temp instances to data after array reference chain
        while(!is_null($parent = $child->_parent) amp;amp; $parent->_temp[$child->_key] === $child){
            $parent->_data[$child->_key] = $parent->_temp[$child->_key];
            $child  = $parent;
        }
        // drop temp
        foreach($child->_temp as amp;$temp){
            unset($temp);
        }
        if(is_null($offset)){
            $this->_data[] = is_array($value)
                ? new self($value, null, $this)
                : $value;
        }else{
            $this->_data[$offset] = is_array($value)
                ? new self($value, $offset, $this)
                : $value;
        }
    }

    public function offsetExists($offset){
        return isset($this->_data[$offset]);
    }

    public function offsetUnset($offset){
        unset($this->_data[$offset]);
    }

}
  

Ответ №1:

Вам нужно использовать второй класс, реализующий ArrayAccess , для использования вместо ваших массивов. Затем вы сможете управлять тем, что добавляется в массив с помощью offsetSet() метода:

 class ReadOnlyArray implements ArrayAccess {
    private $container = array();
    public function __construct(array $array) {
        $this->container = $array;
    }
    public function offsetSet($offset, $value) {
        throw new Exception('Read-only');
    }
    public function offsetExists($offset) {
        return isset($this->container[$offset]);
    }
    public function offsetUnset($offset) {
        unset($this->container[$offset]);
    }
    public function offsetGet($offset) {
        if (! array_key_exists($offset, $this->container)) {
            throw new Exception('Undefined offset');
        }
        return $this->container[$offset];
    }
}
  

Затем вы можете инициализировать свой ReadOnlyArray с помощью исходного массива:

 $readOnlyArray = new ReadOnlyArray(array('foo', 'bar'));
  

Комментарии:

1. Спасибо Бенджамину Морелю ; Как я упоминал cweiske , я подумал; Я начинаю думать, что объектный движок PHP нуждается в пересмотре; подобные случаи, а также отсутствие поддержки amp;offsetGet() довольно расстраивают.

2. Также спасибо за обновление; Я ценю предложение по коду. Как я также упоминал в своем комментарии к cweiske , я также боролся с ArrayAccess , но по другой причине. Я отказываюсь от своей попытки ArrayAccess потерпеть фиаско, но проверьте обновление моего вопроса, чтобы увидеть это.

3. Не очень понятно, что не так с вашей реализацией, если вы не говорите нам, чего вы от нее ожидаете и что не работает 🙂

4. Верно. ( Я редактировал его, и это более или менее не связано, просто подумал, что поделюсь ) Это работает почти так, как ожидалось; никаких уведомлений не выдается при обращении к несуществующим индексам массива или при записи в содержащие массивы ( данного вложенного элемента ). Проблема в том, что isset всегда возвращается значение true, а при увеличении несуществующего индекса массива временный объект преобразуется в int ( выдает ошибку ) Я надеюсь, что PHP скоро добавит поддержку __toInt , __toArray и т.д. Сейчас я мало что могу с этим поделать.

5. @Benjamin Morel: Тот кусок кода, который я опубликовал; хотя я сделал это просто так, ради интереса, мне интересно — возможно ли и то, чего я пытаюсь достичь там? В вашем предоставленном ответе вы создаете исключение, когда смещение не существует. Я работаю в среде с наибольшим количеством сообщений об ошибках ( -1 ) и, конечно, уведомления рассылаются всякий раз, когда я указываю на несуществующие индексы массива. Я пытаюсь преодолеть это, поскольку назначение этой структуры данных является свободным, без строгих ограничений, налагаемых отчетами об ошибках. Есть какие-нибудь идеи?

Ответ №2:

Вы не могли бы вернуть по ссылке, что решило бы проблему изменяемости, но не позволило бы изменять некоторые значения, которые разрешено изменять.

В качестве альтернативы вам также нужно обернуть каждый возвращаемый массив в ArrayAccess — и запретить доступ на запись туда.

Комментарии:

1. Спасибо cweiske ; Я вроде как понял это. Я тоже боролся с ArrayAccess . Я стал поклонником самого высокого уровня отчетов об ошибках ( error_reporting(-1); ) и изо всех сил боролся с PHP, чтобы прекратить выдавать уведомления для одного типа класса при обращении к несуществующим индексам массива / записи в них. Я думаю, я мог бы просто оставить ответственность за то, чтобы клиентский программист не изменял значения.