Implementing the ArrayAccess Interface

This is the second part of an occasional series looking at practical uses for the PHP SPL and predefined interfaces. The first post in the series looked at implementing the Countable interface, this one will examine the ArrayAccess interface.

What is the ArrayAccess Interface?

ArrayAccess allows you to treat an object that implements it as if it is an array for the purposes of setting, unsetting and retrieving data from it. Please note the emphasis in the last sentence! ArrayAccess does not make an object behave like an array in any other way. If you pass an object that implements ArrayAccess to a PHP array function such as in_array() you’ll still get an error. This will become a little clearer with some of the examples below.

To implement ArrayAccess you need to implement four methods: offsetExists, offsetGet, offsetSet and offsetUnset. ArrayAccess::offsetExists must return a boolean, offsetGet can return any valid PHP type while offsetSet and offsetUnset should not return any value. Once you implement these methods you can treat an object as if it is an array for the purposes of saving and retrieving properties. Here’s a brief example.

  1: class Foo implements ArrayAccess {
  3: 	protected $_values = array();
  5: 	public function offsetExists($offset)
  6: 	{
  7: 		return array_key_exists($offset, $this->_values);
  8: 	}
 10: 	public function offsetGet($offset)
 11: 	{
 12: 		return $this->offsetExists($offset) ? $this->_values[$offset]:NULL;
 13: 	}
 15: 	public function offsetSet($offset, $value)
 16: 	{
 17: 		$this->_values[$offset] = $value;
 18: 	}
 20: 	public function offsetUnset($offset)
 21: 	{
 22: 		if ($this->offsetExists($offset)
 23: 			unset($this->_values[$offset]);
 24: 	}
 25: }
 27: $bar = new Foo();
 28: $baz = isset($bar['baz']); //Calls offsetExists and returns false
 29: $bar['baz'] = 1; //Calls offsetSet and adds a new member to the _values array with the key of 'baz' and the value of 1
 30: $baz = isset($bar['baz']); //true
 31: echo $bar['baz']; //Calls offsetGet and prints 1.
 32: unset($bar['baz']);//Calls offsetUnset and removes the value from the internal _values array

Hopefully this should all be fairly self-explanatory and you can see how ArrayAccess can allow you to treat an object like an array. Once again, note that this only allows you to use the array notation to set, unset, retrieve and find out if values are set in the internal _values property. If you want to count the number of values set you’d need to implement Countable and if you wanted to be able to iterate over the object you’d need to implement Iterator or IteratorAggregate. Even then the object could not be passed to PHP array functions.

The above example is also incredibly simplistic. There’s nothing to say that the internal storage for the properties being set has to be an array. Likewise, the logic in your implementation of the ArrayAccess methods can be as simple or as complicated as you need.

Hang on a minute…

In reality the only thing that ArrayAccess gives you that can’t be achieved using PHP’s magic methods is the ability to use array notation. Apart from that there’s nothing here that can’t be achieved with the magic __get(), __set(), __isset() and __unset() methods. So, the question here has to be why bother with ArrayAccess at all when it only provides syntactic sugar? This is a hard one to answer and really comes down to a matter of personal preference. I personally quite like the syntax ArrayAccess gives, especially when coupled with the Countable and Iterator interfaces. For me as well it also gives me a different mindset. I’m used to my IDE providing me with code completion, which of course doesn’t work with magic methods. I always find it a little unnerving when the code completion doesn’t appear, even when I know that it’s not working because of PHP’s ‘magic’. This isn’t a problem with the array syntax that ArrayAccess provides. Of course this is a very subjective viewpoint and those of you reading this will I’m sure have or develop your own opinions on this.

A practical example

One place where ArrayAccess is very useful is in an object where you have a large number of arbitrary values that you need to store and retrieve. I’ve recently been doing a lot of work with the SalesNet CRM system for a client through their SOAP API. The SalesNet API exposes a massive amount of functionality and a lot of the methods can accept a large number of fields to set data for or retrieve data from. SalesNet also allows it’s customers to create custom defined fields and these can both be set and retrieved through the API.

Since there are so many possible values that can be set and retrieved this is a perfect case for using ArrayAccess. I simple pass the name of the SalesNet field that I’m setting or retrieving data from as the ‘array key’. If I’m storing the data in the object to be sent to SalesNet I can retrieve the key and value pairs and process them as needed before making the SOAP call.

Things can get a little more interesting when retrieving data from SalesNet. The response payload is a large lump of XML in most cases. I’ve often resorted to processing this with XPath to get to the nodes I interested in and storing these as an internal property of the object. I can then use offsetGet and offsetExists to work with the data, usually transforming it in some way before returning it. I also often implement the Countable interface to return the number of results. Consider the following (simplified) example:

  1: class SalesNetDemo implements ArrayAccess, Countable
  2: {
  3: 	//Array of SimpleXML objects following processing of the response.
  4: 	protected $_data = array();
  6: 	protected $_soap;
  8: 	public function __construct($soap)
  9: 	{
 10: 		$this->_soap = $soap;
 11: 	}
 13: 	public function doSoapCall()
 14: 	{
 15: 		//Code to do a Soap call and process the results here using XPath.
 16: 	}
 18: 	public function count()
 19: 	{
 20: 		return count($this->_data);
 21: 	}
 23: 	public function offsetExists($offset)
 24: 	{
 25: 		return (isset($this->_data->$offset) &&
                 '' !== (string) $this->_data->$offset);
 26: 	}
 28: 	public function offsetGet($offset)
 29: 	{
 30: 		return (isset($this->_data->$offset) &&
                 '' !== (string) $this->_data->$offset) ? (string) $this->_data->$offset : NULL;
 31: 	}
 34:     	public function offsetSet ($offset, $value)
 35:     	{
 36:         	throw new BadMethodCallException(sprintf(
 37:             		'Values cannot be changed in this class, thrown in %s',
 38:             		__METHOD__
 39:         	));
 40:     	}
 42:     	public function offsetUnset ($offset)
 43:     	{
 44:         	throw new BadMethodCallException(sprintf(
 45:             		'Values cannot be changed in this class, thrown in %s',
 46:             		__METHOD__
 47:         	));
 48:     	}
 49: }
 51: $foo = new SalesNetDemo(new SalesNetSoapObject);
 52: $foo->doSoapCall();
 53: if(count($foo)) {
 54: 	//Results retrieved from the API.
 55: 	//Try to get a valueand echo it.
 56: 	echo $foo['CDF12345'];
 57: }

Again, this is a pretty simple example. The object is only designed to allow data to be retrieved and read, which is why offsetSet and offsetUnset throw exceptions. A real world example might allow the same object to amend the data through the API. The real point of the example though is to show you how the data can be transformed before returning it from the object. Again, the example is deliberately simplistic but you can get the idea of the kinds of things that can be achieved. This is also an example as to how multiple predefined interfaces can be combined to achieve some pretty powerful results.

I hope you’ve enjoyed this brief, and simple, look at ArrayAccess. Let me know in your comments how you you’ve used ArrayAccess in your own code. I’ll add another post as soon as I have time looking at the Iterator and IteratorAggregate interfaces and I’ll also try to add an example that implements Countable, ArrayAccess and Iterator to show how they can work together.

5 thoughts on “Implementing the ArrayAccess Interface

  1. – thank you for your tutorial, but after starting with second example I don’t understand to much, that’s because am a PHP beginner, please, could you present a functional example? (using with a small XML file … and as for me, no SOAP)

Leave a Reply