With PHP 7 you can choose to write much more type-safe code than before, thanks to scalar type hints and return types.

function repeat(string $text, int $times) : string;

But what about arrays? There’s still only the generic “array” type hint, you cannot specify what’s in the array. For the IDE, you can add PhpDoc comments:

/** * @return User[] */ function allUsers() : array;

Now IDEs like PhpStorm can help with code completion for items in the returned array. But we cannot benefit from any checks at runtime, like with real type hints.

For arguments, there is a partial workaround, using variadic arguments. Take the following function

/** * @param User[] $users */ function deleteUsers(array $users);

With variadic arguments we can rewrite it to

function deleteUsers(User ...$users);

Usage also changes, to deleteUsers(...$users); In this call, the argument $users will be “unpacked” into single variables, and in the method itself “packed” back into an array $users . Each item is validated to be of type User . $users can also be an iterator, it will be converted to an array.

Unfortunately there is no similar workaround for return types, and it only works for the last argument.

See also: Type hinting in PHP 7 – array of objects

I’ve used this technique a lot in PHP 7 code, but I’ve found another one that’s even better and does not come with the mentioned flaws:



Collection objects

Every time I need an array of objects, I create a class instead. Here’s a simple example:

class Users extends ArrayIterator { public function __construct(User ...$users) { parent::__construct($users); } public function current() : User { return parent::current(); } public function offsetGet($offset) : User { return parent::offsetGet($offset); } }

This collection extends ArrayIterator , so that it can be used almost like an array: iterating with foreach and accessing elements with [] . Array functions cannot be used but with iterator_to_array() it can be converted to a normal array.

Note that you can only change return type hints to be more restrictive than in the parent class. So overriding offsetSet would not work and the object will be in an unvalidated state until each item is accessed.

If we don’t need the ArrayAccess interface to access elements with [] , there’s a solution for that: encapsulate the array iterator and only expose what you need:

class Users extends IteratorIterator { public function __construct(User ...$users) { parent::__construct(new ArrayIterator($users)); } public function current() : User { return parent::current(); } }

(did you ever wonder, why IteratorIterator exists? Here I found a use case!)

This Users object can only be created with User elements, thanks to the variadic constructor arguments. Passing anything else to the constructor will result in a catchable TypeError .

new Users($user1, $user2, $user3);

And it’s even immutable, it cannot be changed after instantiation. But if we need to change it, we now can add type-safe methods for that, like:

public function add(User $user) { $this->getInnerIterator()->append($user); } public function set(int $key, User $user) { $this->getInnerIterator()->offsetSet($key, $user); }

You can also make it countable (because we know, the inner iterator implements Countable ):

public function count() : int { return $this->getInnerIterator()->count(); }

Array functions

As mentioned before, if you need array functions, you can always convert the object to an array with iterator_to_array() . But there’s another solution which often makes more sense: move the function into the collection class. For sorting functions it’s easy because they are already part of ArrayIterator , so we can do it the same way as with count() above.

But let’s take another example and merge two user collections:

public function merge(Users $other) { return new Users( array_merge( iterator_to_array($this), iterator_to_array($other) ) ); }

Now it can be used as

$allUsers = $goodUsers->merge($badUsers);

When adding methods to the collection objects, be as specific as possible to move logic into the collection that belongs there. For example, instead of creating a generic uasort() method, create exactly the sort method(s) that you need:

public function sortByAge() { $this->getInnerIterator()->uasort( function(User $a, User $b) { return $a->age() <=> $b->age(); } ); }

That makes the client code much clearer (something that would not have been possible with arrays):

$users->sortByAge()

With filter/map/reduce methods, we also have the option to work with collection pipelines, as I explained in a previous blog post: Collection Pipelines in PHP

Domain Logic

When I need a collection of objects, I often create a simple class first, extending IteratorIterator , like in the example above. The obvious advantage is type safety, but it also makes it much easier to add domain logic in the right place later. As soon as I have methods that operate on a collection of objects, I can add them directly to the collection class, where they naturally belong to. Otherwise they might end up in some utility or helper class (Stop using Helpers!), or even in the client code that uses the collection.

Consider this part of a card game:

class AwesomeCardGame { /** @var Card[] */ private $stock; /** @var Card[] */ private $pile; /** * shuffle discard pile and put back to stock */ public function turnPile() { $this->stock = $this->pile $this->pile = []; $this->turnAllCards($this->stock); shuffle($this->stock); } private function turnAllCards(array $cards) { foreach ($cards as $card) { $card->turn(); } } }

It mixes high level game logic with low level implementation details. With a card collection object we can move methods that operate on the cards there, and keep the Game class at one abstraction level:

class AwesomeCardGame { /** @var Cards */ private $stock; /** @var Cards */ private $pile; /** * shuffle discard pile and put back to stock */ public function turnPile() { $this->pile->moveTo($this->stock); $this->stock->turnAll(); $this->stock->shuffle(); } }

Interfaces

It’s often useful to define the collection as interface first. It extends the Iterator interface and contains at least the current() method with specified return type.

Minimum example:

interface Users extends \Iterator { public function current() : User; }

With the default implementation as wrapped ArrayIterator :

final class UsersArray implements Users extends \IteratorIterator { public function __construct(User ...$users) { parent::__construct(new ArrayIterator($users)); } public function current() : User { return parent::current(); } }

Why is it useful?

Exchangeable implementation

First, if your code is going to be used by third parties, they will be able to swap out the implementation. My favorite example are lazy loading collections with generators for better performance with large collections:

final class LazyLoadingUsers implements Users extends \IteratorIterator { public function __construct(Generator $generator) { parent::__construct($generator); } public function current() : User { return parent::current(); } }

which then can be instantiated like this:

$userGenerator = function(PDOStatement $stmt) { while ($user = $stmt->fetchObject(User::class)) { yield $user; } } new LazyLoadingUsers( $userGenerator($pdo->query('SELECT * FROM users')) );

Here the User objects are not fetched from the database before you actually iterate over the lazy loading collection. Since generators can only be iterated once, the collection won’t keep all objects in memory at once.

Extensibility with decorators

Second, you will be able to extend the class with decorators, that wrap the original instance and implement the same interface, but add additional behavior. You can even use the collection to decorate all items on the fly.

Let’s say the default Card implementation in our awesome card game has a __toString() method that returns card suit and rank as plain text, and added a decorator for a HTML representation:

final class HtmlCard implements Card { /** @var Card */ private $card; public function __construct(Card $card) { $this->card = $card; } public function __toString() { $plain = parent::__toString(); return sprintf( '<img src="%s" alt="%s" />', $this->imageUrl($plain) $plain ); } private function imageUrl(string $plain) : string { // ... } }

Now we can write a decorator for the collection as well, that takes any Cards collection and decorates its elements with HtmlCard :

final class HtmlCards implements Cards { /** @var Cards */ private $cards; public function __construct(Cards $cards) { $this->cards = $cards; } public function current() : Card { return new HtmlCard(parent::current()); } public function next() { $this->cards->next(); } public function rewind() { $this->cards->rewind(); } public function valid() { return $this->cards->valid(); } public function key() { return $this->cards->key(); } }

Conclusion

That was lots of code. To wrap it up:

Variadic arguments can be used to type hint a single array argument

Implementing basic collection objects as array replacement is very simple with IteratorIterator

You can convert them with iterator_to_array() , but it’s better to move array methods into the collection instead

, but it’s better to move array methods into the collection instead Once you have collection objects, they naturally attract business logic, as you will see that they are the right place for methods that operate on its elements

With interfaces, you gain flexibility. For examply you can replace array based collections with generator based collections or add decorators

You can do more cool stuff with collections, see: Collection pipelines