我有一个实现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
。
我知道ArrayAccess
通常不能用于多维数组,但是在这个或任何有点干净的实现中是否存在允许所需功能?
我能想到的最好的想法是使用一个字符作为分隔符并在set
和unset
中对其进行测试并采取相应的行动。虽然如果你处理的是深度变化,这会变得非常难看。
有谁知道为什么exists
和get
有效,以便复制功能?
感谢任何人提供的帮助。
答案 0 :(得分:18)
通过将public function offsetGet($name)
更改为public function &offsetGet($name)
(通过添加引用返回)来解决问题,但会导致致命错误( “ ArrayTest :: offsetGet()的声明必须与ArrayAccess :: offsetGet()”的声明兼容。
PHP作者不久前搞砸了这个课程,现在他们won't change it in sake of backwards compatibility:
我们发现这是不可解决的 没有炸毁界面和 创建BC或提供 额外的支持接口 引用,从而创建一个 内心的噩梦 - 实际上我没有 看看我们可以做到这一点的方式。 因此我们决定执行 原创设计,不允许 引用完整。
修改:如果您仍然需要该功能,我建议您使用魔术方法(__get()
,__set()
等),因为__get()
通过引用返回值。这会将语法更改为以下内容:
$arrTest->test['bar'] = 5;
当然不是理想的解决方案,但我想不出更好的解决方案。
更新:此问题为fixed in PHP 5.3.4,ArrayAccess现在按预期工作:
从PHP 5.3.4开始,放松了原型检查,并且可以通过引用返回此方法的实现。这样就可以间接修改ArrayAccess对象的重载数组维度。
答案 1 :(得分:6)
这个问题实际上是可以解决的,完全是功能性的。
来自对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);
?>
这将为您提供一个可以被视为数组的对象(主要是参见代码中的注释),它支持多维数组set / get / unset。
答案 2 :(得分:3)
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
ORIGINAL如下 - 这是不正确的:
您的offsetGet
实施必须返回一个参考,以便它发挥作用。
public function &offsetGet($name) {
return $this->_arr[$name];
}
对于内部等效内容,请参阅here。
由于没有类似于get_property_ptr_ptr,你应该在类似写入的上下文(类型BP_VAR_W,BP_VAR_RW和BP_VAR_UNSET)中返回引用(在Z_ISREF意义上)或代理对象(请参阅get处理程序),尽管它不是强制性的。如果在类似写入的上下文中调用read_dimension,例如$ val =&amp; $ obj ['prop'],并且既不返回引用也不返回对象,引擎会发出通知。显然,返回引用不足以使这些操作正常工作,修改返回的zval实际上有必要产生一些效果。请注意,$ obj ['key'] =&amp; $ a这样的分配仍然是不可能的 - 因为那个维度实际上可以存储为zvals(可能是也可能不是这种情况)和两个间接级别
总之,涉及编写或取消子属性子维度的操作调用offsetGet,而不是offsetSet,offsetExists或offsetUnset。
答案 3 :(得分:2)
解决方案:
<?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);
}
}
答案 4 :(得分:1)
我用这个解决了它:
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']);
请注意,我不检查offset是否为null,如果是数组,则其大小必须为&gt; 1。
答案 5 :(得分:0)
主要根据Dakota的解决方案*我想分享我对它的简化。
*)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];
我做了一些改变:
正如我所介绍的那样,这篇文章更适用于像我这样不那么先进的人。
编辑:这仅适用于在实例化期间设置的单元格,而之后无法添加新单元格。
答案 6 :(得分: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"