(Un) set boyutlu ArrayAccess?

6 Cevap php

I ArrayAccess uygulayan bir sınıf var ve ben bunun çok boyutlu bir dizi ile çalışmak için çalışıyorum. exists ve get çalışır. set ve unset rağmen bana bir sorun veriyoruz.

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

I know $_arr bunu doğrudan erişebilir nedenle sadece kamu yapılmış olabilir, ama benim uygulama için istenen ve özel değil.

Son 2 satır bir hata atmak: Notice: Indirect modification of overloaded element.

I ArrayAccess sadece genellikle çok boyutlu diziler ile çalışmak değil biliyorum, ama istenen işlevselliği sağlayacak bu veya herhangi biraz temiz uygulanması etrafında yine de var mı?

Ben bir ayırıcı olarak bir karakter kullanarak ve set bunun için test ve unset ve buna göre hareket edilir ile gelebilir iyi fikir. Eğer bir değişken derinlikte uğraşıyoruz eğer bu gerçekten hızlı gerçekten çirkin olur ama.

Herkes neden exists bilmek ve get belki işlevselliği üzerinde kopyalamak olarak çalışır mı?

Herhangi için teşekkürler herkes sunabilir yardımcı olur.

6 Cevap

Sorun could be but o {"(Ölümcül Hata neden olur, public function offsetGet($name) (referans getiri ekleyerek) public function &offsetGet($name) değiştirerek çözüldü [(4)]} ").

PHP yazarlar bir süre önce bu sınıf ile berbat ve şimdi onlar won't change it in sake of backwards compatibility:

We found out that this is not solvable without blowing up the interface and creating a BC or providing an additional interface to support references and thereby creating an internal nightmare - actually i don't see a way we can make that work ever. Thus we decided to enforce the original design and disallow references completley.

Eğer hala bu işlevselliği gerekiyorsa Edit:, Çünkü {[(1)], (__get(), __set(), vb) yerine, sihirli yöntemini kullanarak öneririm } referans değerini döndürür. Bu böyle bir şey söz dizimi değişecektir:

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

Elbette değil ideal bir çözüm, ama daha iyi birini düşünemiyorum.

Update: Bu sorun fixed in PHP 5.3.4 ve beklendiği gibi oldu ArrayAccess şimdi çalışıyor:

PHP 5.3.4 ile başlayarak, prototip kontroller rahat ve bu yöntemin uygulamaları referans olarak dönmek için mümkün edilmiştir. Bu ArrayAccess olası nesnelerinin aşırı dizisi boyutlara dolaylı değişiklikler yapar.

Bu sorun nasıl olması gerektiği tamamen işlevsel, aslında çözülebilir.

ArrayAccess dokümantasyon bir yorumun 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"

?>

Daha sonra bu sınıfı genişletir, gibi pek yapabilirsiniz:

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

?>

Bu size çok boyutlu dizi set / get / unset destekleyen bir dizi (çoğunlukla, kodda nota bakınız) gibi tedavi edilebilir bir nesneyi verecektir.

EDIT: Alexander Konstantinov cevaba bakınız. Ben benzer __ olsun sihirli yöntemi, düşünüyordum, ama aslında doğru uygulandığından. Yani sınıfın bir iç uygulama olmadan bunu yapamam.

EDIT2: Dahili uygulama:

NOT: Bu tamamen masturbatory, ama yine de buraya gidiyor iddia olabilir:

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

Şimdi bu komut:

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

verir:

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

ORİJİNAL şu - bu yanlıştır:

O iş için offsetGet uygulama bir başvuru döndürmek gerekir.

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

Iç eşdeğeri için bkz: here.

Get_property_ptr_ptr hiçbir benzer beri var, bunu zorunlu olmasa da, yazma gibi bağlamlarda veya bir proxy nesnesi (Z_ISREF anlamında) bir başvuru (get işleyici bakınız) (tip BP_VAR_W, BP_VAR_RW ve BP_VAR_UNSET) dönmek gerek. Read_dimension gibi $ val gibi bir yazma gibi bağlamında denir = & ediliyorsa $ Obj ['prop'], ve bir başvuru ne bir nesneyi ne dönmek, motor bir haber yayarlar. Açıkçası, bir referans dönen bu işlemleri düzgün çalışması için yeterli değil, iade zval değiştirerek aslında bazı etkiye sahip olması gerekmektedir. Bu bir aslında zvals (ki veya durum olmayabilir) ve yönlendirme iki seviyeleri olarak depolanabilir olması boyutları gerekir için - örneğin $ obj ['key'] = & $ a kadar atamaları hala mümkün olmadığını unutmayın .

Özetle, bir alt özellik arama offsetGet alt boyut değil offsetSet, offsetExists veya offsetUnset yazarken veya unseting içerir işlemleri.

Çözüm:

http://www.mozilla.org/MPL/ * * @author Dillen / Steffen */

namespace Kütüphanesi;

/** * 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);
}

}

Ben bu kullanarak çözdü:

sınıf Colunas ArrayAccess {uygular

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

}

Yani bunu birlikte kullanabilirsiniz:

$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 eğer kontrol yok unutmayın null, ve bir dizi ise, o boyutu> 1 olmalıdır.

Temelde Dakota'in çözüme göre * Ben benim sadeleştirme paylaşmak istiyorum.

*) Dakota en benim için en anlaşılır biriydi ve sonuç oldukça büyük (- diğerleri) büyük oldukça benzer görünüyor.

Yani, burada ne oluyor anlamakta onların zorluklar var benim gibi olanlar için:

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

Ben bazı değişiklikler yaptım:

  • Bu sürece bir gerçek (Dakota'in durumda ilişkisel olarak) diziye nesneyi dönüştürmek istemiyorum gibi hemen bir amacı olduğu gibi, toArray-fonksiyonunu silindi.
  • bu sürece sizin nesneyi klonlamak istemiyorum gibi hemen bir amacı olduğu gibi, klon-şey silindi.
  • genişletilmiş sınıf ve aynı değişkenler değiştirildi: bana daha anlaşılır görünüyor. veya daha fazla boyutlu (ve tabii ki de ilişkisel olmayan) 'dizileri' - - en azından uzun bir ile instanciate gibi özellikle ben DimensionalArrayAccess-sınıf dizi-gibi hatta 3 için nesneye erişim verdiğini vurgulamak istiyorum İhtiyacınız boyutların sayısını sayma dizisi.
  • Bu Gördüğünüz gibi ardışık offsetSet fonksiyonu kendisini çağıran gibi DimensionalArrayAccess-sınıf (ise Örnek sınıf kendisi, bir kurucu değişkene bağlı olmadığını vurgulamak benim için önemli görünüyor son.

Ben tanıtıldı gibi, bu yazı benim gibi çok gelişmiş değil olanlar için oldukça olduğunu.

EDIT: sonradan yeni hücreleri eklemek mümkün değildir oysa bu sadece, örnekleme sırasında ayarlanır hücreler için çalışıyor.