У меня есть реализация класса 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
Я знаю $_arr
мог просто быть обнародован так, Вы могли получить доступ к нему непосредственно, но для моей реализации это не является желаемым и является частным.
Последние 2 строки бросают ошибку: Notice: Indirect modification of overloaded element
.
Я знаю ArrayAccess
просто обычно не работает с многомерными массивами, но там так или иначе вокруг этого или какой-либо несколько чистой реализации, которая позволит желаемую функциональность?
Лучшая идея, которую я мог придумать, использует символ в качестве разделителя и тестирует на него в set
и unset
и действие соответственно. Хотя это становится действительно ужасным действительно быстрый, если Вы имеете дело с переменной глубиной.
Делает любой знает почему exists
и get
работайте, чтобы, возможно, скопировать по функциональности?
Спасибо за любую справку любой может предложить.
Проблема может быть решена путем изменения общедоступной функции offsetGet ($ name)
на общедоступная функция и offsetGet ($ name)
(путем добавления возврата по ссылке), , но это вызовет фатальную ошибку (« Объявление ArrayTest :: offsetGet () должно быть совместимо с объявлением ArrayAccess :: offsetGet () ») .
Авторы PHP облажались с этим классом некоторое время назад, и теперь они не будут менять его ради обратной совместимости :
Мы обнаружили, что это не разрешимо не взрывая интерфейс и создание BC или предоставление дополнительный интерфейс для поддержки ссылки и тем самым создавая внутренний кошмар - на самом деле я не увидеть, как мы когда-нибудь сможем заставить эту работу работать. Таким образом, мы решили обеспечить соблюдение оригинальный дизайн и запретить ссылки полные.
Изменить: Если вам все еще нужна эта функциональность, я бы предложил вместо этого использовать магический метод ( __ get ()
, __ set ()
и т. Д.), Потому что __ get ()
возвращает значение по ссылке. Это изменит синтаксис на что-то вроде этого:
$arrTest->test['bar'] = 5;
Не идеальное решение, конечно, но я не могу придумать лучшего.
Обновление: Эта проблема была исправлена в PHP 5.3.4 , и теперь ArrayAccess работает должным образом:
Начиная с PHP 5.3.4, проверки прототипов были ослаблены, и это возможно для реализаций. этого метода для возврата по ссылке.Это делает возможными косвенные изменения размеров перегруженного массива объектов ArrayAccess.
РЕДАКТИРОВАТЬ: См. Ответ Александра Константинова. Я думал о магическом методе __get, который аналогичен, но на самом деле реализован правильно. Таким образом, вы не можете сделать это без внутренней реализации вашего класса.
РЕДАКТИРОВАТЬ2: Внутренняя реализация:
ПРИМЕЧАНИЕ: Вы можете утверждать, что это чисто мастурбаторный метод, но в любом случае вот оно:
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
дает:
test/bar is set
2
test/baz is not set
5
ОРИГИНАЛ следует - это неверно:
Ваша реализация 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, по-прежнему невозможны - для этого нужно, чтобы измерения действительно сохранялись как zval (что может быть, а может и не иметь место) и два уровня косвенности.
В итоге, операции, которые включают запись или отмену под измерения суб-свойства, вызывают offsetGet, а не offsetSet, offsetExists или offsetUnset.