ArrayAccess, многомерные (ООН), устанавливают?

У меня есть реализация класса 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 работайте, чтобы, возможно, скопировать по функциональности?

Спасибо за любую справку любой может предложить.

16
задан anomareh 21 May 2010 в 10:59
поделиться

2 ответа

Проблема может быть решена путем изменения общедоступной функции 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.

19
ответ дан 30 November 2019 в 17:38
поделиться

РЕДАКТИРОВАТЬ: См. Ответ Александра Константинова. Я думал о магическом методе __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.

3
ответ дан 30 November 2019 в 17:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: