Making Classes Iterable with ArrayAccess in PHP

I'm writing quite a lot API wrappers in PHP, and most GET index responses are paginated, so usually I return a PaginatedResponse class to the user of the wrapper, that has some different properties such as: items, page, totalPages and so on. However, for responses that are not paginated, I return a collection of items. To avoid confusion and different syntax, I use ArrayAccess, which allows the user to iterate a class.

Example

Before implementation

<?php 

$posts = $client->posts()->get(); // PaginatedResponse

foreach($posts->items as $post) {
    // do stuff
}

After implementation

<?php 

$posts = $client->posts()->get(); // PaginatedResponse w/ ArrayAccess

foreach($posts as $post) {
    // do stuff
}

It may not be much of a difference, but I use it to ensure consistency; especially for users that are not using an IDE with type-hinting.

Using ArrayAccess

You will have to start out by implementing ArrayAccess, like so:

<?php 

namespace Responses;

class PaginatedResponse implements \ArrayAccess {
    public $items = [];
}

Now, if you're using an IDE such as PhpStorm, you might get warnings telling you to add required methods: offsetGet, offsetUnset, offsetSet and offsetExists, so let's add those:

<?php

namespace Responses;

class PaginatedResponse implements \ArrayAccess {
    public $items = [];

    public function offsetSet($offset, $value)
    {
        if ($offset === null) {
            $this->items[] = $value;
        } else {
            $this->items[$offset] = $value;
        }
    }

    public function offsetExists($offset)
    {
        return isset($this->items[$offset]);
    }

    public function offsetUnset($offset)
    {
        unset($this->items[$offset]);
    }

    public function offsetGet($offset)
    {
        return $this->items[$offset] ?? null;
    }
}

The above is an example containing all the required methods and code to use ArrayAccess.

That's it. You can add in extra functionality, or even extract this to another class or trait.

Why even bother?

If you're building closed source applications, I'd argue that this is unnecessary, but if you're building open source, this could make your package easier to use. It provides consistent code by not making the user loop $class->get()->items in some cases and $class->get() in other cases, while still providing the extra functionality your class might bring; for example my PaginatedResponse allows the user to get total count, remaining pages, load next/previous page and more, and the usage is still the same as another response class.

Alternatives

You could also use Iterator.

Show Comments