What good are ArrayObjects?
Ever since I had my first encounter with the SPL, it took me a while to understand the actual usefulness of the ArrayObject. Its use isn’t obvious, because it only reveals itself if you want to follow a strict discipline of class encapsulation. Otherwise it doesn’t offer any advantage over the standard PHP array.
A lot of the confusion comes from the lack of documentation. An ArrayObject, the documentation says, is “an array wrapper”, that “allows to recursively iterate over Arrays and public Object properties”. This isn’t very useful, and it doesn’t explain at all what is the ArrayObject actually good for. Iteration over arrays and objects has been long supported simply with the foreach construct:
$arr = array( 'a', 'b', 'c' ); $arr[0] = 'A'; $arr[] = 'd'; echo count( $arr ); foreach( $arr as $letter ) echo $letter;
And this is how you’d do it with an ArrayObject:
$arrObject = new ArrayObject( array( 'a', 'b', 'c' ) ); $arrObject[0] = 'A'; $arrObject[] = 'd'; echo count( $arrObject ); foreach( $arrObject as $letter ) echo $letter;
Both snippets have the same output. You can use the ArrayObject just like you would use the array because it implements several interfaces from the SPL:
- The
IteratorAggregateinterface, which lets foreach iterate over its contents by returning anIterator - The
Countableinterface, that tells thecount()function what to return when used on the object - And the
ArrayAccessinterface, that gives an object the ability to be accessed using the array syntax.
These interfaces are very useful when implemented in your objects, but for the purpose of a simple “array wrapper”, it just adds the overhead of having an object, and then an iterator object to iterate over it. Yet, its exactly what Zend is encouraging:
The ArrayObject class is a powerful tool for iterating through and accessing arrays. Its practical uses are infinite; use it as a wrapper for all arrays: simple arrays, arrays of database records, arrays of objects, etc. Consider the listing in Example 9. Continuing the PartyMember examples from the earlier listings, I fetch data from a database, creating an array of PartyMember objects, from which I then create an ArrayObject object. While this example shows simple iteration with a foreach loop (which I could do when it was a normal array), using ArrayObject provides access to tools that are not accessible to non-object arrays without the use of procedural code and functions. For example, with the ArrayObject, it is possible to sort the array, modify elements or append new ones, and iterate through the results.
This is nice and all, but the only concrete reason they give for using an ArrayObject is, apparently, to perpetuate the object oriented master-race, while oppressing those horrible, evil and icky functions. And this is good enough reason if you’re an OOP purist, and there’s nothing wrong with dreaming of a nice, object oriented, well structured PHP. For me, it just doesn’t cut it. There are reasons why the ArrayObject is inferior in this purpose.
While the overhead is probably insignificant, there’s the thing that an ArrayObject isn’t actually an array, it just behaves like one on occasion. This means that it’s not compatible with most places where you’d normally use an array. You can’t, for example, use most of PHP’s array functions on it directly. Some of the functions, like sorting, are available in the class, but most of them aren’t. For those, you have to extract the internal array, do your operations on it, and then exchange it back in. For me, this seems too much to bother:
$arrObject = new ArrayObject( array( 1, 2, 3 ) ); $array = $arrObject->getArrayCopy(); $array = array_reverse( $array ); $arrObject->exchangeArray( $array );
So what good is the ArrayObject?
Like I said before, it’s got to do with class encapsulation.
We’ve all heard how it’s Evil to directly expose properties of a class. You may or may not accept this as an evangelical truth, but if you do, you might have come across a situation where you need to allow outsiders to interact with an array in your class.
class MyClass { private $list = array(); public getList() { return $this->list; } }
The first problem arises in the fact that arrays are passed by value in PHP, meaning, a copy of the array will be passed out from the function, and that means that changing the result of the getList() method won’t actually change the values in your class. To be able to write to the array, you can either provide a setter, implement methods in your class to do adding and removing from the array or pass it out by reference.
class MyClassWithSetter { private $list = array(); public getList() { return $this->list; } public setList( array $list ) { $this->list = $list; } } class MyClassWithAccessors { private $list = array(); public getList() { return $this->list; } public addToList( $item ) { $this->list[] = $item; } } class MyClassByReference { private $list = array(); public &getList() { return $this->list; } // Note the & }
Neither of these options are very attractive. The first one requires extra code on the outside to get the array, and then set it back in. The second one forces you to implement array access code for every array you want to expose, and the third one is basically exposing the internal array to any kind of modification. There’s nothing stopping someone from doing something like this:
$mc = new MyClassByReference(); $list = $mc->getList(); unset( $list );
Since $list is a reference, unsetting it will also unset $mc’s $list, and unless you check for that everywhere you use it, it can be catastrophic.
And then, there’s the problem of actually using the class. Regardless of whether you pass as a value or as a reference, you still can’t access it without putting it into a variable on its own. This, for example, is invalid syntax:
$mc = new MyClassByReference(); $mc->getList()[] = 'Item 1'; // Invalid if ( $mc->getList()[0] == 'Item 1' ) // Invalid { unset( $mc->getList()[0] ); // Invalid }
The right way to do this would be:
$mc = new MyClassByReference(); $list = $mc->getList(); $list[] = 'Item 1'; if ( $list[0] == 'Item 1' ) { unset( $list[0] ); }
Tolerable, but not optimal.
ArrayObject to the rescue
The solution comes, dragged right out of other OOP languages, in the form of the ArrayObject. Objects, in PHP5, by default, are passed around between functions by making a copy of the reference. That means that if you pass out an object variable out of a function, while it’s a copy of the original variable, it still points to the same instance of the object. This means we no longer have to pass by reference when we expose our list!
class MyClassWithArrayObject { private $list; public __construct() { $this->list = new ArrayObject(); } public getList() { return $this->list; } } $mc = new MyClassWithArrayObject(); $mc->getList()->append( 'Item 1' ); if ( $mc->getList()->getOffset(0) == 'Item 1' ) { $mc->getList()->unsetOffset(0); }
And because the ArrayObject has the OOP interface, you no longer have to extract it into a local variable first!
We could stop here, but we still have the issue of someone passing in invalid data into the list. For this, we can simply extend our ArrayObject.
class StringArrayObject extends ArrayObject { public function append( $value ) { if ( !is_string( $value ) ) throw new InvalidArgumentException( 'Value must be a string' ); parent::append( $value ); } public function offsetSet( $index, $newval ) { if ( !is_string( $newval ) ) throw new InvalidArgumentException( 'Newval must be a string' ); parent::offsetSet( $index, $newval ); } public function exchangeArray( $array ) { foreach( $array as $value ) { if ( !is_string( $value ) ) throw new InvalidArgumentException( 'Value must be a string' ); } parent::exchangeArray( $array ); } } class MyClass { private $list; public function __construct() { $this->list = new StringArrayObject(); } public function getList() { return $this->list; } } $mc = new MyClass(); // these will work. $mc->getList()->append( 'item' ); $mc->getList()->exchangeArray( array( 'item', 'item' ) ); // these won't. $mc->getList()->append( 123 ); $mc->getList()->exchangeArray( array( 123, 'item' ) );
This is much better. Obviously, you can include any kind of validation logic you want.
It’s not a perfect world. For one thing, I would still prefer to use “[]” rather than “append()“, and “[0]” instead of “offsetGet(0)“. For this, I could use PHP’s “overloading”* to make actual accessors, but I personally think it’s ugly and stupid having to implement plumbing logic for my class that should be in the language itself. Maybe one day PHP will have proper accessors, or support the “getList()[]” syntax.
Furthermore, there’s the issue that the ArrayObject doesn’t support most of the array manipulation functions of PHP, so it certainly can’t be used as an object-oriented array replacement (that would be pretty sweet, really), but given that in its state, the ArrayObject is really only useful for exposing lists from classes that won’t likely be manipulated extensively, its behavior suffices for the purpose. It’s still a good step for a more object oriented PHP.
4 Responses to “What good are ArrayObjects?”
Comment from Erik Hansen
Time August 1, 2009 at 8:39 am
Very comprehensive post. Helped give me a better understanding of the benefits of the ArrayObject class. Thanks!
Comment from brad
Time October 18, 2009 at 7:33 pm
PHP makes me want to shoot myself in the head! Array handling is just another reason why?
Comment from Ted Wood
Time October 25, 2009 at 11:49 pm
I’m looking forward to leveraging the strengths of an OOP approach to working with arrays in my PHP programming. I have yet to use ArrayObject. Thanks for the insights into how it can be used properly.
Pingback from PHP: Clear/Delete/Remove operations on ArrayObject class « Turboflash’s Blog
Time February 7, 2009 at 6:46 am
[...] Labs Blog has actually written a very interesting article on the topic of ArrayObject so you might like to read it up. Some foundation here is actually based [...]