代码之家  ›  专栏  ›  技术社区  ›  anomareh

数组处理多维(un)集?

  •  15
  • anomareh  · 技术社区  · 14 年前

    我有一个课程实施 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 可以公开,这样您就可以直接访问它,但对于我的实现来说,它是不需要的,而且是私有的。

    最后两行引发错误: Notice: Indirect modification of overloaded element .

    我知道 阵列存取 通常情况下,它不适用于多维数组,但是否有任何围绕这个或任何稍微干净的实现来实现所需的功能?

    我能想到的最好的办法是使用一个字符作为分隔符,并在 设置 未定式 并据此采取行动。如果你处理的是一个可变的深度,这会很快变得很难看。

    有人知道为什么吗 存在 得到 为了复制功能而工作?

    感谢任何人提供的帮助。

    7 回复  |  直到 8 年前
        1
  •  18
  •   Alexander Konstantinov    11 年前

    问题 可以是 通过更改解决 public function offsetGet($name) public function &offsetGet($name) (通过引用添加返回值) 但是 它将导致致命错误(“ arrayTest::offsetget()的声明必须与arrayAccess::offsetget()的声明兼容。 “”。

    PHP的作者在一段时间前搞砸了这门课,现在他们 won't change it in sake of backwards compatibility :

    我们发现这是无法解决的 不炸毁界面 创建BC或提供 要支持的附加接口 引用,从而创建 内部噩梦-实际上我没有 看一看我们能让这一切继续下去的方法。 因此我们决定执行 原创设计和不允许 完全引用。

    编辑: 如果您仍然需要这个功能,我建议您改用magic方法( __get() , __set() 等等),因为 γ-() 按引用返回值。这将把语法改成如下:

    $arrTest->test['bar'] = 5;
    

    当然不是一个理想的解决方案,但我想不出更好的解决方案。

    更新: 这个问题是 fixed in PHP 5.3.4 arrayaccess现在按预期工作:

    从PHP5.3.4开始,对原型的检查就比较宽松了,并且可以通过引用返回此方法的实现。这使得间接修改arrayaccess对象的重载数组维度成为可能。

        2
  •  6
  •   Dakota dmg    11 年前

    这个问题实际上是可以解决的,完全是功能性的。

    来自arrayaccess文档的注释 here :

    <?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);
    
    ?>
    

    这将为您提供一个可以被视为数组的对象(大多数情况下,请参见代码中的注释),该对象支持多维数组集/获取/取消设置。

        3
  •  3
  •   Artefacto    14 年前

    编辑:见Alexander Konstantinov的回复。我在考虑“获得魔法”的方法,这是类似的,但实际上是正确实现的。因此,如果没有类的内部实现,就不能这样做。

    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
    

    给予:

    test/bar is set
    2
    test/baz is not set
    5
    

    原文如下——这是不正确的:

    你的 offsetGet 实现必须返回一个引用,以便它工作。

    public function &offsetGet($name) {
        return $this->_arr[$name];
    }
    

    有关内部等效项,请参见 here .

    由于没有类似的get_property_ptr_ptr,所以您应该在类似于write的上下文(类型bp_var_w、bp_var_rw和bp_var_unset)中返回引用(从z_isref的意义上讲)或代理对象(参见get handler),尽管这不是强制的。如果在类似于写的上下文(如$val=&$obj['prop'])中调用read_维度,并且既不返回引用也不返回对象,则引擎将发出通知。显然,返回引用不足以使这些操作正常工作,修改返回的zval确实有一些效果是必要的。请注意,诸如$obj['key']=&$a这样的分配仍然是不可能的,因为它需要维度作为zvals(可能是,也可能不是)和两个间接级别来实际存储。

    总之,涉及写入或取消设置子属性的子维度的操作调用offsetget,而不是offsetset、offsetxists或offsetup。

        4
  •  2
  •   Twifty Andy    8 年前

    解决方案:

    <?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);
        }
    }
    
        5
  •  1
  •   Twifty Andy    8 年前

    我用这个解决了它:

    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。

        6
  •  0
  •   sasha    11 年前

    主要是根据达科他的解决方案*我想分享我对它的简化。

    *)达科他是我最能理解的一个,结果非常好(其他的看起来非常相似)。

    所以,对于像我这样的人,他们很难理解这里发生的事情:

    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];
    

    我做了一些改变:

    • 删除了to array函数,因为它没有直接的用途,只要你不想把你的对象转换成一个真正的(在dakota的案例中是关联的)数组。
    • 删除了克隆对象,因为它没有立即的目的,只要你不想克隆你的对象。
    • 重新命名了扩展类和相同的vars:对我来说似乎更容易理解。特别要强调的是,DimensionAlarrayAccess类允许类似数组的方式访问对象,即使是对于3维或更多维(当然也是非关联的)“数组”—至少只要用一个数组来计算所需的维数。
    • 最后,我要强调的是,正如您所看到的,示例类本身并不依赖于构造函数变量,而DimensionAlarrayAccess类(就像它在offsetset函数中递归地调用自己一样)。

    正如我介绍的,这篇文章更适合像我这样不太先进的人。

    编辑:这只适用于在实例化期间设置的单元格,而不可能在以后添加新的单元格。

        7
  •  0
  •   Gajus    11 年前
    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"