У меня есть класс, реализующий ArrayAccess
и я пытаюсь заставить его работать с многомерным массивом. exists
и get
работу. set
и unset
дают мне проблему, хотя.
class ArrayTest implements ArrayAccess { private $_arr = array( 'test' => array( 'bar' => 1, 'baz' => 2 ) ); public function offsetExists($name) { return isset($this->_arr[$name]); } public function offsetSet($name, $value) { $this->_arr[$name] = $value; } public function offsetGet($name) { return $this->_arr[$name]; } public function offsetUnset($name) { unset($this->_arr[$name]); } } $arrTest = new ArrayTest(); isset($arrTest['test']['bar']); // Returns TRUE echo $arrTest['test']['baz']; // Echo's 2 unset($arrTest['test']['bar']; // Error $arrTest['test']['bar'] = 5; // Error
сclass ArrayTest implements ArrayAccess { private $_arr = array( 'test' => array( 'bar' => 1, 'baz' => 2 ) ); public function offsetExists($name) { return isset($this->_arr[$name]); } public function offsetSet($name, $value) { $this->_arr[$name] = $value; } public function offsetGet($name) { return $this->_arr[$name]; } public function offsetUnset($name) { unset($this->_arr[$name]); } } $arrTest = new ArrayTest(); isset($arrTest['test']['bar']); // Returns TRUE echo $arrTest['test']['baz']; // Echo's 2 unset($arrTest['test']['bar']; // Error $arrTest['test']['bar'] = 5; // Error
Я знаю, что $_arr
можно просто обнародовать, чтобы вы могли получить доступ к нему напрямую, но для моей реализации это нежелательно и является конфиденциальным.
Последние 2 строки выдают ошибку: Notice: Indirect modification of overloaded element
.
Я знаю, что ArrayAccess
как правило, не работает с многомерными массивами, но все равно существует эта или какая-то несколько чистая реализация, которая позволит использовать нужные функции?
Лучшей идеей, которую я мог бы придумать, является использование символа в качестве разделителя и тестирование на него в set
и unset
и действие соответственно. Хотя это становится действительно уродливым очень быстро, если вы имеете дело с переменной глубиной.
Кто-нибудь знает, почему exists
и работает, чтобы, возможно, копировать функциональность?
Спасибо за любую помощь, которую любой может предложить.
Проблема может быть решена путем изменения public function offsetGet($name)
на public function &offsetGet($name)
(путем добавления возврата по ссылке), но это приведет к фатальной ошибке (« Объявление из массива»: offsetGet () должно быть совместимо с то из ArrayAccess :: offsetGet () ").
PHP-разработчики придумали этот класс некоторое время назад, и теперь они не изменят его ради обратной совместимости :
Мы выяснили, что это не разрешимо, не взорвав интерфейс и не создавая BC или предоставляя дополнительный интерфейс для поддержки ссылок и тем самым создавая внутренний кошмар – на самом деле я не вижу способа, которым мы можем сделать эту работу когда-либо. Таким образом, мы решили обеспечить первоначальный дизайн и запретить ссылки completeley.
Изменить. Если вам все еще нужна эта функциональность, я бы предложил вместо этого использовать магический метод ( __get()
, __set()
и т. Д.), Потому что __get()
возвращает значение по ссылке. Это изменит синтаксис на что-то вроде этого:
$arrTest->test['bar'] = 5;
Конечно, не идеальное решение, но я не могу придумать лучшего.
Обновление: эта проблема была исправлена в PHP 5.3.4, и теперь ArrayAccess работает так, как ожидалось:
Начиная с PHP 5.3.4, проверки прототипов были ослаблены, и реализации этого метода можно вернуть по ссылке. Это делает возможным косвенные изменения в размерах перегруженных массивов объектов ArrayAccess.
Эта проблема действительно разрешима, полностью функциональна, как она должна быть.
Из комментария к документации ArrayAccess здесь :
<?php // sanity and error checking omitted for brevity // note: it's a good idea to implement arrayaccess + countable + an // iterator interface (like iteratoraggregate) as a triplet class RecursiveArrayAccess implements ArrayAccess { private $data = array(); // necessary for deep copies public function __clone() { foreach ($this->data as $key => $value) if ($value instanceof self) $this[$key] = clone $value; } public function __construct(array $data = array()) { foreach ($data as $key => $value) $this[$key] = $value; } public function offsetSet($offset, $data) { if (is_array($data)) $data = new self($data); if ($offset === null) { // don't forget this! $this->data[] = $data; } else { $this->data[$offset] = $data; } } public function toArray() { $data = $this->data; foreach ($data as $key => $value) if ($value instanceof self) $data[$key] = $value->toArray(); return $data; } // as normal public function offsetGet($offset) { return $this->data[$offset]; } public function offsetExists($offset) { return isset($this->data[$offset]); } public function offsetUnset($offset) { unset($this->data); } } $a = new RecursiveArrayAccess(); $a[0] = array(1=>"foo", 2=>array(3=>"bar", 4=>array(5=>"bz"))); // oops. typo $a[0][2][4][5] = "baz"; //var_dump($a); //var_dump($a->toArray()); // isset and unset work too //var_dump(isset($a[0][2][4][5])); // equivalent to $a[0][2][4]->offsetExists(5) //unset($a[0][2][4][5]); // equivalent to $a[0][2][4]->offsetUnset(5); // if __clone wasn't implemented then cloning would produce a shallow copy, and $b = clone $a; $b[0][2][4][5] = "xyzzy"; // would affect $a's data too //echo $a[0][2][4][5]; // still "baz" ?>
с<?php // sanity and error checking omitted for brevity // note: it's a good idea to implement arrayaccess + countable + an // iterator interface (like iteratoraggregate) as a triplet class RecursiveArrayAccess implements ArrayAccess { private $data = array(); // necessary for deep copies public function __clone() { foreach ($this->data as $key => $value) if ($value instanceof self) $this[$key] = clone $value; } public function __construct(array $data = array()) { foreach ($data as $key => $value) $this[$key] = $value; } public function offsetSet($offset, $data) { if (is_array($data)) $data = new self($data); if ($offset === null) { // don't forget this! $this->data[] = $data; } else { $this->data[$offset] = $data; } } public function toArray() { $data = $this->data; foreach ($data as $key => $value) if ($value instanceof self) $data[$key] = $value->toArray(); return $data; } // as normal public function offsetGet($offset) { return $this->data[$offset]; } public function offsetExists($offset) { return isset($this->data[$offset]); } public function offsetUnset($offset) { unset($this->data); } } $a = new RecursiveArrayAccess(); $a[0] = array(1=>"foo", 2=>array(3=>"bar", 4=>array(5=>"bz"))); // oops. typo $a[0][2][4][5] = "baz"; //var_dump($a); //var_dump($a->toArray()); // isset and unset work too //var_dump(isset($a[0][2][4][5])); // equivalent to $a[0][2][4]->offsetExists(5) //unset($a[0][2][4][5]); // equivalent to $a[0][2][4]->offsetUnset(5); // if __clone wasn't implemented then cloning would produce a shallow copy, and $b = clone $a; $b[0][2][4][5] = "xyzzy"; // would affect $a's data too //echo $a[0][2][4][5]; // still "baz" ?>
Затем вы можете расширить этот класс следующим образом:
<?php class Example extends RecursiveArrayAccess { function __construct($data = array()) { parent::__construct($data); } } $ex = new Example(array('foo' => array('bar' => 'baz'))); print_r($ex); $ex['foo']['bar'] = 'pong'; print_r($ex); ?>
Это даст вам объект, который можно рассматривать как массив (в основном, см. Примечание в коде), который поддерживает многомерный массив set / get / unset.
EDIT: см. Ответ Александра Константинова. Я думал о методе __get magic, который аналогичен, но был фактически реализован правильно. Таким образом, вы не можете сделать это без внутренней реализации вашего класса.
EDIT2: Внутренняя реализация:
ПРИМЕЧАНИЕ. Вы можете утверждать, что это чисто мастурбация, но в любом случае здесь:
static zend_object_handlers object_handlers; static zend_object_value ce_create_object(zend_class_entry *class_type TSRMLS_DC) { zend_object_value zov; zend_object *zobj; zobj = emalloc(sizeof *zobj); zend_object_std_init(zobj, class_type TSRMLS_CC); zend_hash_copy(zobj->properties, &(class_type->default_properties), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*)); zov.handle = zend_objects_store_put(zobj, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC); zov.handlers = &object_handlers; return zov; } /* modification of zend_std_read_dimension */ zval *read_dimension(zval *object, zval *offset, int type TSRMLS_DC) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zval *retval; void *dummy; if (zend_hash_find(&ce->function_table, "offsetgetref", sizeof("offsetgetref"), &dummy) == SUCCESS) { if(offset == NULL) { /* [] construct */ ALLOC_INIT_ZVAL(offset); } else { SEPARATE_ARG_IF_REF(offset); } zend_call_method_with_1_params(&object, ce, NULL, "offsetgetref", &retval, offset); zval_ptr_dtor(&offset); if (!retval) { if (!EG(exception)) { /* ought to use php_error_docref* instead */ zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name); } return 0; } /* Undo PZVAL_LOCK() */ Z_DELREF_P(retval); return retval; } else { zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); return 0; } } ZEND_MODULE_STARTUP_D(testext) { zend_class_entry ce; zend_class_entry *ce_ptr; memcpy(&object_handlers, zend_get_std_object_handlers(), sizeof object_handlers); object_handlers.read_dimension = read_dimension; INIT_CLASS_ENTRY(ce, "TestClass", NULL); ce_ptr = zend_register_internal_class(&ce TSRMLS_CC); ce_ptr->create_object = ce_create_object; return SUCCESS; }
теперь этот скрипт:
<?php class ArrayTest extends TestClass implements ArrayAccess { private $_arr = array( 'test' => array( 'bar' => 1, 'baz' => 2 ) ); public function offsetExists($name) { return isset($this->_arr[$name]); } public function offsetSet($name, $value) { $this->_arr[$name] = $value; } public function offsetGet($name) { throw new RuntimeException("This method should never be called"); } public function &offsetGetRef($name) { return $this->_arr[$name]; } public function offsetUnset($name) { unset($this->_arr[$name]); } } $arrTest = new ArrayTest(); echo (isset($arrTest['test']['bar'])?"test/bar is set":"error") . "\n"; echo $arrTest['test']['baz']; // Echoes 2 echo "\n"; unset($arrTest['test']['baz']); echo (isset($arrTest['test']['baz'])?"error":"test/baz is not set") . "\n"; $arrTest['test']['baz'] = 5; echo $arrTest['test']['baz']; // Echoes 5
с<?php class ArrayTest extends TestClass implements ArrayAccess { private $_arr = array( 'test' => array( 'bar' => 1, 'baz' => 2 ) ); public function offsetExists($name) { return isset($this->_arr[$name]); } public function offsetSet($name, $value) { $this->_arr[$name] = $value; } public function offsetGet($name) { throw new RuntimeException("This method should never be called"); } public function &offsetGetRef($name) { return $this->_arr[$name]; } public function offsetUnset($name) { unset($this->_arr[$name]); } } $arrTest = new ArrayTest(); echo (isset($arrTest['test']['bar'])?"test/bar is set":"error") . "\n"; echo $arrTest['test']['baz']; // Echoes 2 echo "\n"; unset($arrTest['test']['baz']); echo (isset($arrTest['test']['baz'])?"error":"test/baz is not set") . "\n"; $arrTest['test']['baz'] = 5; echo $arrTest['test']['baz']; // Echoes 5
с<?php class ArrayTest extends TestClass implements ArrayAccess { private $_arr = array( 'test' => array( 'bar' => 1, 'baz' => 2 ) ); public function offsetExists($name) { return isset($this->_arr[$name]); } public function offsetSet($name, $value) { $this->_arr[$name] = $value; } public function offsetGet($name) { throw new RuntimeException("This method should never be called"); } public function &offsetGetRef($name) { return $this->_arr[$name]; } public function offsetUnset($name) { unset($this->_arr[$name]); } } $arrTest = new ArrayTest(); echo (isset($arrTest['test']['bar'])?"test/bar is set":"error") . "\n"; echo $arrTest['test']['baz']; // Echoes 2 echo "\n"; unset($arrTest['test']['baz']); echo (isset($arrTest['test']['baz'])?"error":"test/baz is not set") . "\n"; $arrTest['test']['baz'] = 5; echo $arrTest['test']['baz']; // Echoes 5
дает:
test/bar is set 2 test/baz is not set 5
ORIGINAL – это неверно:
Реализация offsetGet
должна возвращать ссылку для ее работы.
public function &offsetGet($name) { return $this->_arr[$name]; }
Для внутреннего эквивалента см. Здесь .
Так как нет аналогичного get_property_ptr_ptr, вы должны вернуть ссылку (в смысле Z_ISREF) или прокси-объекта (см. Обработчик get) в сценариях записи (типы BP_VAR_W, BP_VAR_RW и BP_VAR_UNSET), хотя это необязательно. Если read_dimension вызывается в контексте, подобном записи, например, в $ val = & $ obj ['prop'], и вы не возвращаете ни ссылку, ни объект, двигатель выдает уведомление. Очевидно, что возвращать ссылку недостаточно для правильной работы этих операций, необходимо, чтобы изменение возвращаемого zval действительно имело некоторый эффект. Обратите внимание, что назначения, такие как $ obj ['key'] = & $ a, по-прежнему невозможны – для этого нужны размеры, которые фактически могут быть сохранены как zvals (что может быть или не быть) и два уровня косвенности.
В целом, операции, которые включают в себя запись или прочтение подразмера смещения субобъекта offsetGet, а не offsetSet, offsetExists или offsetUnset.
Решение:
<?php /** * Cube PHP Framework * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * @author Dillen / Steffen */ namespace Library; /** * The application * * @package Library */ class ArrayObject implements \ArrayAccess { protected $_storage = array(); // necessary for deep copies public function __clone() { foreach ($this->_storage as $key => $value) { if ($value instanceof self) { $this->_storage[$key] = clone $value; } } } public function __construct(array $_storage = array()) { foreach ($_storage as $key => $value) { $this->_storage[$key] = $value; } } public function offsetSet($offset, $_storage) { if (is_array($_storage)) { $_storage = new self($_storage); } if ($offset === null) { $this->_storage[] = $_storage; } else { $this->_storage[$offset] = $_storage; } } public function toArray() { $_storage = $this -> _storage; foreach ($_storage as $key => $value) { if ($value instanceof self) { $_storage[$key] = $value -> toArray(); } } return $_storage; } // as normal public function offsetGet($offset) { if (isset($this->_storage[$offset])) { return $this->_storage[$offset]; } if (!isset($this->_storage[$offset])) { $this->_storage[$offset] = new self; } return $this->_storage[$offset]; } public function offsetExists($offset) { return isset($this->_storage[$offset]); } public function offsetUnset($offset) { unset($this->_storage); } }
с<?php /** * Cube PHP Framework * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * @author Dillen / Steffen */ namespace Library; /** * The application * * @package Library */ class ArrayObject implements \ArrayAccess { protected $_storage = array(); // necessary for deep copies public function __clone() { foreach ($this->_storage as $key => $value) { if ($value instanceof self) { $this->_storage[$key] = clone $value; } } } public function __construct(array $_storage = array()) { foreach ($_storage as $key => $value) { $this->_storage[$key] = $value; } } public function offsetSet($offset, $_storage) { if (is_array($_storage)) { $_storage = new self($_storage); } if ($offset === null) { $this->_storage[] = $_storage; } else { $this->_storage[$offset] = $_storage; } } public function toArray() { $_storage = $this -> _storage; foreach ($_storage as $key => $value) { if ($value instanceof self) { $_storage[$key] = $value -> toArray(); } } return $_storage; } // as normal public function offsetGet($offset) { if (isset($this->_storage[$offset])) { return $this->_storage[$offset]; } if (!isset($this->_storage[$offset])) { $this->_storage[$offset] = new self; } return $this->_storage[$offset]; } public function offsetExists($offset) { return isset($this->_storage[$offset]); } public function offsetUnset($offset) { unset($this->_storage); } }
Я решил это, используя это:
class Colunas implements ArrayAccess { public $cols = array(); public function offsetSet($offset, $value) { $coluna = new Coluna($value); if (!is_array($offset)) { $this->cols[$offset] = $coluna; } else { if (!isset($this->cols[$offset[0]])) $this->cols[$offset[0]] = array(); $col = &$this->cols[$offset[0]]; for ($i = 1; $i < sizeof($offset); $i++) { if (!isset($col[$offset[$i]])) $col[$offset[$i]] = array(); $col = &$col[$offset[$i]]; } $col = $coluna; } } public function offsetExists($offset) { if (!is_array($offset)) { return isset($this->cols[$offset]); } else { $key = array_shift($offset); if (!isset($this->cols[$key])) return FALSE; $col = &$this->cols[$key]; while ($key = array_shift($offset)) { if (!isset($col[$key])) return FALSE; $col = &$col[$key]; } return TRUE; } } public function offsetUnset($offset) { if (!is_array($offset)) { unset($this->cols[$offset]); } else { $col = &$this->cols[array_shift($offset)]; while (sizeof($offset) > 1) $col = &$col[array_shift($offset)]; unset($col[array_shift($offset)]); } } public function offsetGet($offset) { if (!is_array($offset)) { return $this->cols[$offset]; } else { $col = &$this->cols[array_shift($offset)]; while (sizeof($offset) > 0) $col = &$col[array_shift($offset)]; return $col; } } }
неclass Colunas implements ArrayAccess { public $cols = array(); public function offsetSet($offset, $value) { $coluna = new Coluna($value); if (!is_array($offset)) { $this->cols[$offset] = $coluna; } else { if (!isset($this->cols[$offset[0]])) $this->cols[$offset[0]] = array(); $col = &$this->cols[$offset[0]]; for ($i = 1; $i < sizeof($offset); $i++) { if (!isset($col[$offset[$i]])) $col[$offset[$i]] = array(); $col = &$col[$offset[$i]]; } $col = $coluna; } } public function offsetExists($offset) { if (!is_array($offset)) { return isset($this->cols[$offset]); } else { $key = array_shift($offset); if (!isset($this->cols[$key])) return FALSE; $col = &$this->cols[$key]; while ($key = array_shift($offset)) { if (!isset($col[$key])) return FALSE; $col = &$col[$key]; } return TRUE; } } public function offsetUnset($offset) { if (!is_array($offset)) { unset($this->cols[$offset]); } else { $col = &$this->cols[array_shift($offset)]; while (sizeof($offset) > 1) $col = &$col[array_shift($offset)]; unset($col[array_shift($offset)]); } } public function offsetGet($offset) { if (!is_array($offset)) { return $this->cols[$offset]; } else { $col = &$this->cols[array_shift($offset)]; while (sizeof($offset) > 0) $col = &$col[array_shift($offset)]; return $col; } } }
сclass Colunas implements ArrayAccess { public $cols = array(); public function offsetSet($offset, $value) { $coluna = new Coluna($value); if (!is_array($offset)) { $this->cols[$offset] = $coluna; } else { if (!isset($this->cols[$offset[0]])) $this->cols[$offset[0]] = array(); $col = &$this->cols[$offset[0]]; for ($i = 1; $i < sizeof($offset); $i++) { if (!isset($col[$offset[$i]])) $col[$offset[$i]] = array(); $col = &$col[$offset[$i]]; } $col = $coluna; } } public function offsetExists($offset) { if (!is_array($offset)) { return isset($this->cols[$offset]); } else { $key = array_shift($offset); if (!isset($this->cols[$key])) return FALSE; $col = &$this->cols[$key]; while ($key = array_shift($offset)) { if (!isset($col[$key])) return FALSE; $col = &$col[$key]; } return TRUE; } } public function offsetUnset($offset) { if (!is_array($offset)) { unset($this->cols[$offset]); } else { $col = &$this->cols[array_shift($offset)]; while (sizeof($offset) > 1) $col = &$col[array_shift($offset)]; unset($col[array_shift($offset)]); } } public function offsetGet($offset) { if (!is_array($offset)) { return $this->cols[$offset]; } else { $col = &$this->cols[array_shift($offset)]; while (sizeof($offset) > 0) $col = &$col[array_shift($offset)]; return $col; } } }
Таким образом, вы можете использовать его с:
$colunas = new Colunas(); $colunas['foo'] = 'Foo'; $colunas[array('bar', 'a')] = 'Bar A'; $colunas[array('bar', 'b')] = 'Bar B'; echo $colunas[array('bar', 'a')]; unset($colunas[array('bar', 'a')]); isset($colunas[array('bar', 'a')]); unset($colunas['bar']);
Обратите внимание, что я не проверяю, является ли смещение нулевым, и если это массив, он должен быть размером> 1.
В основном в соответствии с решением Дакоты * Я хочу поделиться своим упрощением.
*) Дакота был для меня наиболее понятным, и результат довольно велик (- другие, похоже, очень похожи).
Итак, для таких, как я, у кого есть трудности в понимании того, что здесь происходит:
class DimensionalArrayAccess implements ArrayAccess { private $_arr; public function __construct(array $arr = array()) { foreach ($arr as $key => $value) { $this[$key] = $value; } } public function offsetSet($offset, $val) { if (is_array($val)) $val = new self($val); if ($offset === null) { $this->_arr[] = $val; } else { $this->_arr[$offset] = $val; } } // as normal public function offsetGet($offset) { return $this->_arr[$offset]; } public function offsetExists($offset) { return isset($this->_arr[$offset]); } public function offsetUnset($offset) { unset($this->_arr); } } class Example extends DimensionalArrayAccess { function __construct() { parent::__construct([[["foo"]]]); } } $ex = new Example(); echo $ex[0][0][0]; $ex[0][0][0] = 'bar'; echo $ex[0][0][0];
сclass DimensionalArrayAccess implements ArrayAccess { private $_arr; public function __construct(array $arr = array()) { foreach ($arr as $key => $value) { $this[$key] = $value; } } public function offsetSet($offset, $val) { if (is_array($val)) $val = new self($val); if ($offset === null) { $this->_arr[] = $val; } else { $this->_arr[$offset] = $val; } } // as normal public function offsetGet($offset) { return $this->_arr[$offset]; } public function offsetExists($offset) { return isset($this->_arr[$offset]); } public function offsetUnset($offset) { unset($this->_arr); } } class Example extends DimensionalArrayAccess { function __construct() { parent::__construct([[["foo"]]]); } } $ex = new Example(); echo $ex[0][0][0]; $ex[0][0][0] = 'bar'; echo $ex[0][0][0];
Я сделал некоторые изменения:
Как я представил, этот пост скорее для не столь продвинутых, как я.
EDIT: это работает только для ячеек, которые устанавливаются во время создания экземпляра, тогда как после этого невозможно добавить новые ячейки.
class Test implements \ArrayAccess { private $input = []; public function __construct () { $this->input = ['foo' => ['bar' => 'qux']]; } public function offsetExists ($offset) {} public function offsetGet ($offset) {} public function offsetSet ($offset, $value) {} public function offsetUnset ($offset) {} } runkit_method_redefine ('Test', 'offsetGet', '&$offset', 'return $this->input[$offset];'); $ui = new Test; var_dump($ui['foo']['bar']); // string(3) "qux"