Php 阵列访问多维(un)集?

Php 阵列访问多维(un)集?,php,arrays,interface,multidimensional-array,Php,Arrays,Interface,Multidimensional Array,我有一个类实现了ArrayAccess,我正试图让它与多维数组一起工作存在和获取工作设置和取消设置给了我一个问题 class ArrayTest implements ArrayAccess { private $_arr = array( 'test' => array( 'bar' => 1, 'baz' => 2 ) ); public function offset

我有一个类实现了
ArrayAccess
,我正试图让它与多维数组一起工作<代码>存在和
获取
工作<代码>设置和
取消设置
给了我一个问题

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
我知道,
$\u arr
可以公开,这样您就可以直接访问它,但对于我的实现来说,它是不需要的,是私有的

最后两行抛出错误:
注意:间接修改重载元素

我知道,
ArrayAccess
通常不适用于多维数组,但是在这方面还是有什么可以实现所需功能的干净实现吗

我能想到的最好办法是使用一个字符作为分隔符,在
set
unset
中对其进行测试,并相应地执行操作。不过,如果你处理的是可变深度的话,这会很快变得很难看

有人知道为什么
存在
获取
工作以便复制功能吗


感谢任何人提供的帮助。

编辑:请参阅亚历山大·康斯坦蒂诺夫的回复。我想到了uuu 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
原文如下——这是不正确的:

您的
offsetGet
实现必须返回一个引用才能工作

public function &offsetGet($name) {
    return $this->_arr[$name];
}
有关内部等效项,请参见

由于没有类似于get_property_ptr_ptr的方法,因此应该在类似于write的上下文(类型BP_VAR_W、BP_VAR_RW和BP_VAR_UNSET)中返回引用(在Z_ISREF的意义上)或代理对象(请参见get处理程序),尽管这不是强制性的。如果在类似写的上下文中调用read_维度,如$val=&$obj['prop'],并且您既不返回引用也不返回对象,则引擎将发出通知。显然,返回一个引用不足以使这些操作正常工作,修改返回的zval实际上有一定的效果是必要的。注意,$obj['key']=&$a这样的赋值仍然是不可能的,因为需要将维度实际存储为zvals(可能是也可能不是这样)和两级间接寻址


总之,涉及写入或取消设置子属性的子维度的操作调用offsetGet,而不是offsetSet、offsetExists或offsetUnset。

通过将
公共函数offsetGet($name)
更改为
公共函数和offsetGet($name)
(通过添加引用返回)可以解决问题,但它将导致致命错误(“ArrayTest::offsetGet()的声明必须与ArrayAccess::offsetGet()的声明兼容”)

PHP作者不久前把这个类搞砸了,现在他们:

我们发现这是无法解决的 在不破坏界面和 创建业务连续性或提供 要支持的附加接口 引用,从而创建 内心的噩梦——其实我不知道 想办法让这件事永远成功。 因此,我们决定执行 原始设计和不允许 完全引用

编辑:如果您仍然需要该功能,我建议使用magic方法(
\uuuu get()
\uu set()
,等等),因为
\uu get()
通过引用返回值。这会将语法更改为以下内容:

$arrTest->test['bar'] = 5;
当然不是一个理想的解决方案,但我想不出更好的解决方案

更新:此问题已解决,ArrayAccess现在按预期工作:

从PHP5.3.4开始,原型检查被放松,这个方法的实现可以通过引用返回。这使得间接修改ArrayAccess对象的重载数组维度成为可能


我用以下方法解决了这个问题:

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;
        }
    }
} 
请注意,我不检查偏移量是否为null,如果它是数组,则其大小必须大于1。

解决方案:

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

这个问题实际上是可以解决的,它应该是完全实用的

从ArrayAccess文档的注释中:


然后可以扩展该类,如下所示:

<?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。

主要根据Dakota的解决方案*我想分享我对它的简化

*)对我来说,达科他州的是最容易理解的一个,结果非常好(其他的看起来非常相似)

所以,对于像我这样的人来说,他们很难理解这里发生了什么:

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];
我做了一些改变:

  • 删除了toArray函数,因为它没有直接的用途,只要您不想将对象转换为实数组(在Dakota的案例中是关联数组)
  • 删除了克隆对象,因为只要不想克隆对象,它就没有直接的用途
  • 重命名扩展类和相同的变量:对我来说似乎更容易理解。我特别想强调的是,DimensionalArrayAccess类允许对对象进行类似数组的访问,即使是3维或更高维(当然也是非关联的)“数组”——至少只要将其实例化为一个计算所需维数的数组即可
  • 最后,我要强调的是,正如您所看到的,示例类本身并不依赖于构造函数变量,而DimensionalArrayAccess类依赖于构造函数变量(因为它在offsetSet函数中递归地调用自己)
正如我所介绍的,这篇文章是为像我这样不太先进的人写的

编辑:这仅适用于单元格w
<?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);

?>
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 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"