Интерфейс PHP объявляет методы, которые класс обещает предоставить, без всякого кода: interface Shape { public function area(): float; }. Класс, который пишет implements Shape, обязан определить area(), а любая функция с подсказкой типа Shape примет такой класс.
describe() не знает и не интересуется, какой класс получила. Она полагается только на обещание интерфейса, поэтому новый Triangle implements Shape будет с ней работать без изменения хотя бы одной строки.
Программирование на уровне интерфейса
Обычная причина написать интерфейс это позволить коду зависеть от возможности, а не от конкретного класса. Здесь сервис заказов принимает любой Notifier, поэтому один и тот же код может отправлять email в продакшене и собирать сообщения в тесте:
Реализация нескольких интерфейсов
Класс наследует не больше чем от одного родителя, но может реализовать столько интерфейсов, сколько нужно. Перечислите их после implements через запятую и определите методы их всех:
JsonSerializable один из встроенных интерфейсов PHP: его реализация определяет, что выдаст json_encode() для объекта.
Константы интерфейса
Интерфейс может объявлять константы. Каждый реализующий класс их наследует, а читать их можно через имя интерфейса или имя класса:
В PHP 8.3 у интерфейсов не может быть свойств, только методы и константы. Методы могут быть статическими: public static function fromArray(array $data): static; в интерфейсе обязывает каждый класс определить этот статический метод. Для фиксированного набора связанных значений enum часто подходит лучше, чем список констант интерфейса.
Интерфейс может наследовать другие интерфейсы
Интерфейсы тоже используют extends, и, в отличие от классов, могут наследовать сразу несколько. Класс, реализующий дочерний интерфейс, должен определить методы всей цепочки:
Встроенные интерфейсы: Countable, IteratorAggregate, Stringable
В PHP есть интерфейсы, которые встраивают объекты в сам язык. Countable заставляет работать count($obj), IteratorAggregate заставляет работать foreach, а Stringable автоматически реализуется любым классом с методом __toString():
Забытый метод
Если класс заявляет, что реализует интерфейс, но пропускает метод, PHP останавливается с фатальной ошибкой, где названо имя недостающего метода:
Добавьте метод total(), и скрипт заработает. Абстрактный класс может оставить методы интерфейса неопределёнными, и именно этот выход подсказывает сообщение об ошибке.
Интерфейс и абстрактный класс
Оба определяют методы, которые должен иметь класс, и оба можно использовать как подсказку типа. Разница в том, что ещё они могут содержать и сколько их может использовать класс:
| Интерфейс | Абстрактный класс | |
|---|---|---|
| Тела методов | нет (только сигнатуры) | абстрактные и готовые методы |
| Свойства | нет | да |
| Конструктор | можно объявить, но редко полезно | да, с кодом |
| Видимость методов | только public | public, protected, private |
| Константы | да | да |
| Сколько на класс | любое число (implements A, B, C) | один (extends Base) |
| Типичное применение | возможность: Exportable, Countable | общий код для семейства: Model, Controller |
Они хорошо сочетаются: определите интерфейс для подсказки типа и предложите абстрактный базовый класс, который реализует его с общим кодом. Классы, которым нужна другая база, по-прежнему могут реализовать интерфейс напрямую.
Часто задаваемые вопросы
Что такое интерфейс в PHP?
Список сигнатур публичных методов без тел: interface Logger { public function log(string $msg): void; }. Класс, который пишет implements Logger, обязан определить каждый из этих методов, а любой код с подсказкой типа Logger примет такой класс.
Может ли класс PHP реализовать несколько интерфейсов?
Да. Перечислите их после implements через запятую: class File implements Readable, Writable, Countable. Класс должен определить методы их всех. Так PHP покрывает то, что в других языках делает множественное наследование.
Чем интерфейс отличается от абстрактного класса в PHP?
Интерфейс только объявляет публичные методы (и константы); у него нет свойств и кода методов, и класс может реализовать много интерфейсов. Абстрактный класс может содержать свойства, конструкторы и готовые методы рядом с абстрактными, но класс может наследовать только от одного. Используйте интерфейс для возможности ("можно экспортировать"), абстрактный класс для общего кода в семействе классов.
Могут ли у интерфейса PHP быть свойства?
Не в PHP 8.3: интерфейс может содержать сигнатуры методов и константы (const VERSION = 2;), но не свойства. Объявления свойств в интерфейсах (хуки свойств) появились только в PHP 8.4.
Как проверить, реализует ли объект интерфейс, в PHP?
Используйте instanceof: $obj instanceof Countable истинно, когда класс объекта реализует интерфейс напрямую или через родительский класс. class_implements($obj) возвращает имена всех интерфейсов, которые реализует класс.