Встроенные интерфейсы
Функция count(), цикл foreach, квадратные скобки и json_encode() работают не только с массивами. Объект тоже может их поддерживать — для этого класс реализует один из встроенных интерфейсов PHP.
Countable
Интерфейс Countable позволяет передать объект в 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('чай');
$cart->add('кофе');
echo count($cart); // 2IteratorAggregate
Чтобы объект можно было перебирать в foreach, он возвращает итератор из метода getIterator():
<?php
class Playlist implements IteratorAggregate
{
public function __construct(private array $songs) {}
public function getIterator(): Iterator
{
return new ArrayIterator($this->songs);
}
}
foreach (new Playlist(['Вступление', 'Финал']) as $number => $song) {
echo $number + 1, '. ', $song, "\n";
}ArrayIterator — готовый итератор по массиву. Вместо него метод может быть генератором: yield from $this->songs;.
ArrayAccess
ArrayAccess даёт объекту квадратные скобки. Нужны четыре метода — проверка, чтение, запись и удаление:
<?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']) ? 'есть' : 'нет'; // нетJsonSerializable
По умолчанию json_encode() выводит только открытые свойства объекта. Метод jsonSerialize() позволяет решить самому, что попадёт в 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('Анна', 'secret'), JSON_UNESCAPED_UNICODE); // {"name":"Анна"}Пароль в ответ не попал.
Stringable
Класс с методом __toString() автоматически реализует Stringable. Этот тип удобно указывать в параметрах: функция примет и строку, и объект, умеющий стать строкой:
<?php
class Money
{
public function __construct(private int $amount) {}
public function __toString(): string
{
return number_format($this->amount, 0, '', ' ') . ' ₽';
}
}
function show(string|Stringable $value): void
{
echo $value;
}
show(new Money(12500)); // 12 500 ₽Сводка
| Интерфейс | Что начинает работать |
|---|---|
Countable | count($object) |
IteratorAggregate, Iterator | foreach ($object as …) |
ArrayAccess | $object['key'] |
JsonSerializable | json_encode($object) |
Stringable | echo $object, приведение к строке |
Частая ошибка
Объект с этими интерфейсами похож на массив, но массивом не является. Функции array_map(), in_array(), implode() его не примут. Сначала превратите объект в массив функцией iterator_to_array():
<?php
$list = new ArrayObject(['чай', 'кофе']);
try {
echo implode(', ', array_map('strtoupper', $list));
} catch (TypeError $e) {
echo "Ошибка: объект — не массив\n";
}
echo implode(', ', iterator_to_array($list)); // чай, кофе