implements
Узнайте, как ключевое слово "implements" в PHP обеспечивает соблюдение контрактов интерфейсов. Синтаксис, несколько интерфейсов, константы и типизация.
Ключевое слово implements в PHP
Ключевое слово implements сообщает PHP, что класс берёт на себя обязательство выполнить контракт, определённый интерфейсом. Интерфейс перечисляет сигнатуры методов (и, при необходимости, константы), но не содержит тел методов; класс, реализующий его, обязан предоставить конкретное тело для каждого метода, объявленного в интерфейсе. Если этого не сделать, PHP выбросит фатальную ошибку ещё до запуска программы.
На этой странице рассматриваются синтаксис, реализация нескольких интерфейсов одновременно, константы интерфейсов, типизация по интерфейсам, типичные ошибки, а также различие между implements и extends. Если интерфейсы для вас — новая тема, начните с PHP Interfaces и Что такое OOP.
Синтаксис
interface MyInterface {
public function doSomething(); // signature only — no body
}
class MyClass implements MyInterface {
public function doSomething() {
// the concrete implementation lives here
}
}MyClass implements MyInterface — это обещание: «данный класс предоставляет рабочую версию каждого метода, которого требует MyInterface.» Интерфейс отвечает на вопрос что; класс — на вопрос как.
Базовый пример
<?php
interface Animal {
public function makeSound();
}
class Dog implements Animal {
public function makeSound() {
echo "Woof!";
}
}
class Cat implements Animal {
public function makeSound() {
echo "Meow!";
}
}
$animals = [new Dog(), new Cat()];
foreach ($animals as $animal) {
$animal->makeSound(); // Output: Woof!Meow!
}Поскольку и Dog, и Cat реализуют Animal, любой код, ожидающий Animal, может работать с любым из них, не зная конкретный класс. Это и есть основа полиморфизма в PHP.
Реализация нескольких интерфейсов
В отличие от extends (класс может расширять только один родительский класс), класс может реализовывать любое количество интерфейсов — они перечисляются через запятую. Класс обязан выполнить требования каждого метода из каждого перечисленного интерфейса.
<?php
interface Logger {
public function log(string $message): void;
}
interface Notifier {
public function notify(string $message): void;
}
class AlertService implements Logger, Notifier {
public function log(string $message): void {
echo "LOG: $message\n";
}
public function notify(string $message): void {
echo "NOTIFY: $message\n";
}
}
$service = new AlertService();
$service->log("Disk space low"); // Output: LOG: Disk space low
$service->notify("Disk space low"); // Output: NOTIFY: Disk space lowИменно так PHP получает преимущества множественного наследования (соответствие нескольким контрактам) без его недостатков (неоднозначность унаследованных реализаций).
Константы интерфейсов и типизация
Интерфейс может объявлять константы, и реализующий класс получает к ним доступ как к собственным. Однако главная сила — в типизации: когда параметр типизируется по интерфейсу, принимается любой реализующий его класс, что позволяет свободно менять реализации.
<?php
interface PaymentGateway {
const CURRENCY = "USD";
public function charge(float $amount): bool;
}
class StripeGateway implements PaymentGateway {
public function charge(float $amount): bool {
echo "Charging " . self::CURRENCY . " $amount\n";
return true;
}
}
// Accepts ANY PaymentGateway, not just StripeGateway
function processPayment(PaymentGateway $gateway, float $amount): void {
$gateway->charge($amount);
}
processPayment(new StripeGateway(), 49.99); // Output: Charging USD 49.99Также можно проверить объект на соответствие интерфейсу во время выполнения с помощью instanceof:
var_dump($gateway instanceof PaymentGateway); // bool(true)Распространённые ошибки и подводные камни
- Пропуск метода — фатальная ошибка. Если класс не реализует хотя бы один метод интерфейса, PHP выбросит
Fatal error: Class X contains 1 abstract method and must therefore be declared abstract or implement the remaining methods. Проверка происходит на этапе компиляции, до выполнения какого-либо кода. - Сигнатуры должны быть совместимы. Реализация обязана сохранять типы параметров и возвращаемых значений, объявленные в интерфейсе (по правилам вариантности PHP допускается расширение типов параметров и сужение типов возврата, но несоответствия являются фатальными).
- Методы интерфейса неявно
public. Нельзя реализовать метод интерфейса какprotectedилиprivate. - Интерфейсы могут расширять интерфейсы. Используйте
interface B extends A, чтобы строить на основе другого интерфейса; класс, реализующийB, должен удовлетворять методам обоих. Обратите внимание: здесь используетсяextends, а неimplements.
implements vs extends
Эти ключевые слова легко перепутать:
extends | implements | |
|---|---|---|
| Используется с | одним родительским классом (или интерфейс→интерфейс) | одним или несколькими интерфейсами |
| Наследует код? | да — свойства и тела методов | нет — только контракт (сигнатуры) |
| Сколько? | класс расширяет один класс | класс реализует множество интерфейсов |
Класс может делать и то, и другое одновременно: class Circle extends Shape implements JsonSerializable { ... }. Подробнее о наследовании читайте в PHP extends и, если вам нужны частичные реализации, в абстрактных классах PHP.
Когда использовать implements
Прибегайте к интерфейсу и implements, когда:
- Несколько несвязанных классов должны быть взаимозаменяемы (например, несколько платёжных шлюзов, логгеров или драйверов кэша).
- Нужно типизировать параметр по возможности, а не по конкретному классу, чтобы сохранить слабую связанность кода и упростить тестирование с помощью моков.
- Вы определяете публичный контракт API, которому обязаны следовать другие классы — включая те, что будут написаны позже или другими командами.
Если вместо этого нужно разделить реальный код реализации между связанными классами, используйте наследование классов (extends) или абстрактный класс.