#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, чтобы прекратить выдавать уведомления для одного типа класса при обращении к несуществующим индексам массива / записи в них. Я думаю, я мог бы просто оставить ответственность за то, чтобы клиентский программист не изменял значения.