How to disable a nested array using ArrayObject?

ideone

Code example:

<?php $a = new ArrayObject(); $a['b'] = array('c'=>array('d')); print_r($a); unset($a['b']['c']); print_r($a); 

Exit

 ArrayObject Object ( [b] => Array ( [c] => Array ( [0] => d ) ) ) ArrayObject Object ( [b] => Array ( [c] => Array ( [0] => d ) ) ) 

You noticed that $a['b']['c'] still exists, even after disarming. I would expect $a to have only one value on the left ( b ).

In my real application, the following warning appears:

Indirect modification of an overloaded MyClass element has no effect

Where MyClass continues to ArrayObject . I have a lot of code that depends on the ability to disable nested elements like this, so how can I get this to work?

+8
php arrayobject
source share
4 answers

One way to do it

 <?php $a = new ArrayObject(); $a['b'] = array('c' => array('d')); $d =& $a['b']; unset($d['c']); print_r($a['b']); 

prints:

 Array ( ) 

Think a little longer to explain why the syntax you originally used does not remove the element.

EDIT: Explanation of Behavior

What happens is the unset($a['b']['c']); call unset($a['b']['c']); converted to:

 $temp = $a->offsetGet('b'); unset($temp['c']); 

since $temp is a copy of $a instead of referencing it, PHP uses an internal copy-on-write copy and creates a second array where $temp does not have ['b']['c'] , but $a still does.

OTHER EDITING: reusable code

So, no matter how you function offsetGet($index) it, it seems like trying to overload function offsetGet($index) as function &offsetGet($index) causing trouble; so here is the shortest helper method that I came up with that could add it as a static or instance method to a subclass of ArrayObject , regardless of what your boat floats:

 function unsetNested(ArrayObject $oArrayObject, $sIndex, $sNestedIndex) { if(!$oArrayObject->offSetExists($sIndex)) return; $aValue =& $oArrayObject[$sIndex]; if(!array_key_exists($sNestedIndex, $aValue)) return; unset($aValue[$sNestedIndex]); } 

So, the source code will become

 $a = new ArrayObject(); $a['b'] = array('c' => array('d')); // instead of unset($a['b']['c']); unsetNested($a, 'b', 'c'); print_r($a['b']); 

GIVE ANOTHER CHANGE: OO Solution

OK - So, I must have scrambled b / c this morning. I found an error in my code, and when it was changed, we can implement an OO based solution.

Just so you know what I tried, the segfaults .. extension:

 /// XXX This does not work, posted for illustration only class BadMoxuneArrayObject extends ArrayObject { public function &offsetGet($index) { $var =& $this[$index]; return $var; } } 

The decorator's implementation, on the other hand, works like a charm:

 class MoxuneArrayObject implements IteratorAggregate, ArrayAccess, Serializable, Countable { private $_oArrayObject; // Decorated ArrayObject instance public function __construct($mInput=null, $iFlags=0, $sIteratorClass='') { if($mInput === null) $mInput = array(); if($sIteratorClass === '') $this->_oArrayObject = new ArrayObject($mInput, $iFlags); else $this->_oArrayObject = new ArrayObject($mInput, $iFlags, $sIteratorClass); } // ----------------------------------------- // override offsetGet to return by reference // ----------------------------------------- public function &offsetGet($index) { $var =& $this->_oArrayObject[$index]; return $var; } // ------------------------------------------------------------ // everything else is passed through to the wrapped ArrayObject // ------------------------------------------------------------ public function append($value) { return $this->_oArrayObject->append($value); } public function asort() { return $this->_oArrayObject->asort(); } public function count() { return $this->_oArrayObject->count(); } public function exchangeArray($mInput) { return $this->_oArrayObject->exchangeArray($mInput); } public function getArrayCopy() { return $this->_oArrayObject->getArrayCopy(); } public function getFlags() { return $this->_oArrayObject->getFlags(); } public function getIterator() { return $this->_oArrayObject->getIterator(); } public function getIteratorClass() { return $this->_oArrayObject->getIteratorClass(); } public function ksort() { return $this->_oArrayObject->ksort(); } public function natcassesort() { return $this->_oArrayObject->natcassesort(); } public function offsetExists($index) { return $this->_oArrayObject->offsetExists($index); } public function offsetSet($index, $value) { return $this->_oArrayObject->offsetSet($index, $value); } public function offsetUnset($index) { return $this->_oArrayObject->offsetUnset($index); } public function serialize() { return $this->_oArrayObject->serialize(); } public function setFlags($iFlags) { return $this->_oArrayObject->setFlags($iFlags); } public function setIteratorClass($iterator_class) { return $this->_oArrayObject->setIteratorClass($iterator_class); } public function uasort($cmp_function) { return $this->_oArrayObject->uasort($cmp_function); } public function uksort($cmp_function) { return $this->_oArrayObject->uksort($cmp_function); } public function unserialize($serialized) { return $this->_oArrayObject->unserialize($serialized); } } 

Now this code works as desired:

 $a = new MoxuneArrayObject(); $a['b'] = array('c' => array('d')); unset($a['b']['c']); var_dump($a); 

You still need to modify the code, though ..; I see no way around this.

+11
source share

It seems to me that the "overloaded" bracket operator ArrayObject returns a copy of the nested array, not a reference to the original. Thus, when you call $a['b'] , you get a copy of the internal array that ArrayObject uses to store the data. Further solving it $a['b']['c'] just gives you the "c" element inside the copy, so calling unset() on it does not cancel the "c" element in the original.

ArrayObject implements the ArrayAccess interface , which actually allows the bracket operator to work with the object. The documentation for ArrayAccess::offsetGet indicates that, with PHP 5.3.4, references to the source data in the internal ArrayObject array can be obtained using the =& operator, like quickshiftin specified in its example.

+4
source share

You can use unset($a->b['c']); instead of unset($a['b']['c']); in case there is no big problem for such a replacement for all situations in your project

+1
source share

I seem to have a partial solution. unset seems to work if all nested arrays are instances of ArrayObject . To ensure that all nested arrays are also ArrayObjects, we can extract from this class:

 class ArrayWrapper extends ArrayObject { public function __construct($input=array(), $flags=ArrayObject::STD_PROP_LIST, $iterator_class='ArrayIterator') { foreach($input as $key=>$value) { if(is_array($value)) { $input[$key] = new self($value, $flags, $iterator_class); } } parent::__construct($input, $flags, $iterator_class); } public function offsetSet($offset, $value) { parent::offsetSet($offset, is_array($value) ? new ArrayWrapper($value) : $value); } } 

(updated for recursion, unverified)

And then when you try to add a nested array, it is automatically converted to ArrayWrapper instead.

Unfortunately, many other array functions, such as array_key_exists , do not work in ArrayObjects.

0
source share

All Articles