Built-in interfaces
The count() function, the foreach loop, square brackets and json_encode() work with more than arrays. An object can support them too — the class implements one of PHP's built-in interfaces.
Countable
The Countable interface lets you pass an object to count():
<?php
class Cart implements Countable
{
private array $items = [];
public function add(string $item): void
{
$this->items[] = $item;
}
public function count(): int
{
return count($this->items);
}
}
$cart = new Cart();
$cart->add('tea');
$cart->add('coffee');
echo count($cart); // 2IteratorAggregate
For an object to be looped over with foreach, it returns an iterator from the getIterator() method:
<?php
class Playlist implements IteratorAggregate
{
public function __construct(private array $songs) {}
public function getIterator(): Iterator
{
return new ArrayIterator($this->songs);
}
}
foreach (new Playlist(['Intro', 'Finale']) as $number => $song) {
echo $number + 1, '. ', $song, "\n";
}ArrayIterator is a ready-made iterator over an array. The method can be a generator instead: yield from $this->songs;.
ArrayAccess
ArrayAccess gives an object square brackets. Four methods are needed — checking, reading, writing and removing:
<?php
class Settings implements ArrayAccess
{
private array $data = [];
public function offsetExists(mixed $offset): bool
{
return isset($this->data[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->data[$offset] ?? null;
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->data[$offset] = $value;
}
public function offsetUnset(mixed $offset): void
{
unset($this->data[$offset]);
}
}
$settings = new Settings();
$settings['theme'] = 'dark';
echo $settings['theme'], "\n"; // dark
echo isset($settings['size']) ? 'yes' : 'no'; // noJsonSerializable
By default json_encode() outputs only an object's public properties. The jsonSerialize() method lets you decide what goes into the JSON:
<?php
class User implements JsonSerializable
{
public function __construct(
private string $name,
private string $password,
) {}
public function jsonSerialize(): array
{
return ['name' => $this->name];
}
}
echo json_encode(new User('Anna', 'secret')); // {"name":"Anna"}The password did not get into the response.
Stringable
A class with a __toString() method implements Stringable automatically. The type is handy in parameters: a function accepts both a string and an object that can become one:
<?php
class Money
{
public function __construct(private int $amount) {}
public function __toString(): string
{
return '$' . number_format($this->amount);
}
}
function show(string|Stringable $value): void
{
echo $value;
}
show(new Money(12500)); // $12,500Summary
| Interface | What starts working |
|---|---|
Countable | count($object) |
IteratorAggregate, Iterator | foreach ($object as …) |
ArrayAccess | $object['key'] |
JsonSerializable | json_encode($object) |
Stringable | echo $object, casting to a string |
Common mistake
An object with these interfaces resembles an array but is not one. array_map(), in_array() and implode() do not accept it. Turn the object into an array with iterator_to_array() first:
<?php
$list = new ArrayObject(['tea', 'coffee']);
try {
echo implode(', ', array_map('strtoupper', $list));
} catch (TypeError $e) {
echo "Error: an object is not an array\n";
}
echo implode(', ', iterator_to_array($list)); // tea, coffee