W3docs

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

Эти ключевые слова легко перепутать:

extendsimplements
Используется содним родительским классом (или интерфейс→интерфейс)одним или несколькими интерфейсами
Наследует код?да — свойства и тела методовнет — только контракт (сигнатуры)
Сколько?класс расширяет один класскласс реализует множество интерфейсов

Класс может делать и то, и другое одновременно: class Circle extends Shape implements JsonSerializable { ... }. Подробнее о наследовании читайте в PHP extends и, если вам нужны частичные реализации, в абстрактных классах PHP.

Когда использовать implements

Прибегайте к интерфейсу и implements, когда:

  • Несколько несвязанных классов должны быть взаимозаменяемы (например, несколько платёжных шлюзов, логгеров или драйверов кэша).
  • Нужно типизировать параметр по возможности, а не по конкретному классу, чтобы сохранить слабую связанность кода и упростить тестирование с помощью моков.
  • Вы определяете публичный контракт API, которому обязаны следовать другие классы — включая те, что будут написаны позже или другими командами.

Если вместо этого нужно разделить реальный код реализации между связанными классами, используйте наследование классов (extends) или абстрактный класс.

Практика

Практика
Какова функция ключевого слова 'implements' в PHP?
Какова функция ключевого слова 'implements' в PHP?
Was this page helpful?