Интерфейсы объектов
Интерфейсы объектов разрешают создавать код, который
указывает, какие методы и свойства должен реализовать класс,
без определения реализации этих методов или свойств.
Интерфейсы разделяют пространство имён с классами, трейтами
и перечислениями, поэтому они не могут называться одинаково.
Интерфейсы определяются так же, как классы, но с ключевым словом
interface
вместо слова class
и с методами, ни один из которых не определяет содержимое тела.
Методы интерфейса объявляются общедоступными, что
вытекает из самой природы интерфейса.
Интерфейсы преследуют две взаимодополняющие цели:
-
Разрешают разработчикам создавать объекты разноимённых классов, которые умеют взаимно заменять друг друга,
поскольку реализуют один и тот же интерфейс или интерфейсы.
Интерфейсы часто внедряют в код, когда требуется создать набор служб доступа к базе данных,
платёжных шлюзов или стратегий кеширования. Один класс подменяют другим без изменения кода, который его использует.
-
Разрешают параметру функции или метода принимать и обрабатывать объект, который подчиняется контракту интерфейса,
чтобы не заботиться о том, что ещё умеет делать объект или как его реализовали.
Интерфейсы часто называют
Iterable
, Cacheable
, Renderable
и другими похожими именами, чтобы описать поведение интерфейса.
В интерфейсах также определяют
магические методы, чтобы потребовать от классов,
которые реализуют интерфейс, реализации этих методов.
Замечание:
Лучше не включать конструкторы в интерфейсы,
чтобы не снижать гибкость объекта, который реализует интерфейс,
хотя включение конструкторов и поддерживается.
Конструкторы, кроме того, не соблюдают правила наследования, из-за чего поведение иногда становится противоречивым
и неожиданным.
Оператор implements
Для реализации интерфейса используется оператор implements
.
Класс должен реализовать все методы, описанные в интерфейсе, иначе
произойдёт фатальная ошибка. При желании классы могут реализовывать
более одного интерфейса, разделяя каждый интерфейс запятой.
Внимание
Параметрам в методах класса, в котором реализуется интерфейс, разрешается указывать названия,
которые не совпадают с названиями параметров в методах интерфейса. Но начиная с PHP 8.0 язык поддерживает
именованные аргументы, и код, в котором вызываются методы интерфейса,
часто полагается на названия параметров в интерфейсе.
Поэтому разработчикам рекомендуют указывать в методах те же названия параметров,
что и в реализуемом интерфейсе.
Замечание:
Аналогично классам, интерфейсы расширяют оператором
extends.
Замечание:
Класс, которым реализуется интерфейс, обязан объявить каждый метод интерфейса
по правилам совместимости сигнатур.
Реализация методов обязана следовать правилам совместимости сигнатур
для каждого интерфейса, когда класс реализует больше одного интерфейса,
в котором объявили методы с одинаковым названием. Поэтому при организации иерархии типов PHP-разработчики
пользуются доступной в языке ковариантностью и контравариантностью.
Константы
Интерфейсы поддерживают объявления констант. Константы интерфейсов
работают так же, как константы классов.
До PHP 8.1.0 константы интерфейса нельзя было переопределять в производном классе или интерфейсе.
Properties
Начиная с PHP 8.4.0 в интерфейсах разрешили объявлять свойства.
При объявлении свойств потребуется указать, доступно ли свойство для чтения,
записи или и того, и другого.
Объявление интерфейса применяется только к открытому доступу на чтение и запись.
Класс удовлетворяет свойству интерфейса несколькими способами:
класс определяет открытое свойство,
виртуальное свойство,
которое реализует только тот хук, который соответствует хуку интерфейса,
или определяет readonly
-свойство, которое удовлетворяет свойству интерфейса для чтения.
Однако в классе нельзя ограничивать доступ на запись свойства модификатором readonly
,
если в интерфейсе свойство объявили доступным для записи.
Пример #1 Пример свойств интерфейса
<?php
interface I
{
// Класс, в котором реализуется свойство, ДОЛЖЕН объявить открытое для чтения свойство,
// но объявление свойства в интерфейсе не ограничивает объявление доступа на запись свойства в классе
public string $readable {
get;
}
// Класс, в котором реализуется свойство, должен объявить открытое для записи свойство,
// но объявление свойства в интерфейсе не ограничивает объявление доступа на чтение свойства в классе
public string $writeable {
set;
}
// Класс, в котором реализуется свойство, должен объявить свойство,
// открытое как для чтения, так и для записи
public string $both {
get;
set;
}
}
// Класс реализует каждое из трёх свойств традиционно, без хуков.
// Такая реализация свойств допустима
class C1 implements I
{
public string $readable;
public string $writeable;
public string $both;
}
// Класс реализует каждое из трёх свойств и определяет только те хуки,
// которые потребовал интерфейс. Такая реализация свойств тоже допустима
class C2 implements I
{
private string $written = '';
private string $all = '';
// Класс реализует только хук для чтения, чтобы создать виртуальное свойство.
// Такое определение удовлетворяет требованию «публичной открытости для чтения».
// Свойство недоступно для записи, но интерфейс и не требует открытого доступа для записи
public string $readable {
get => strtoupper($this->writeable);
}
// Интерфейс требует только того, чтобы класс определил свойство, открытое для записи,
// но включение хука для операции чтения тоже допустимо.
// Пример создаёт виртуальное свойство, и это нормально
public string $writeable {
get => $this->written;
set {
$this->written = $value;
}
}
// Свойство требует как операции чтения, так и операции записи,
// поэтому потребуется либо реализовать оба хука, либо разрешить операциям чтения и записи
// поведение по умолчанию
public string $both {
get => $this->all;
set {
$this->all = strtoupper($value);
}
}
}
?>
Примеры
Пример #2 Пример интерфейса
<?php
// Объявляем интерфейс 'Template'
interface Template
{
public function setVariable($name, $var);
public function getHtml($template);
}
// Реализуем интерфейс
// Это будет работать
class WorkingTemplate implements Template
{
private $vars = [];
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach ($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
// Это не сработает
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (Template::getHtml)
// (Фатальная ошибка: Класс BadTemplate содержит 1 абстрактный метод
// и поэтому требуется объявить класс абстрактным (Template::getHtml))
class BadTemplate implements Template
{
private $vars = [];
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
}
?>
Пример #3 Наследование интерфейсов
<?php
interface A
{
public function foo();
}
interface B extends A
{
public function baz(Baz $baz);
}
// Это сработает
class C implements B
{
public function foo() {}
public function baz(Baz $baz) {}
}
// Это не сработает и выдаст фатальную ошибку
class D implements B
{
public function foo() {}
public function baz(Foo $foo) {}
}
?>
Пример #4 Совместимость с несколькими интерфейсами
<?php
class Foo {}
class Bar extends Foo {}
interface A
{
public function myfunc(Foo $arg): Foo;
}
interface B
{
public function myfunc(Bar $arg): Bar;
}
class MyClass implements A, B
{
public function myfunc(Foo $arg): Bar
{
return new Bar();
}
}
?>
Пример #5 Множественное наследование интерфейсов
<?php
interface A
{
public function foo();
}
interface B
{
public function bar();
}
interface C extends A, B
{
public function baz();
}
class D implements C
{
public function foo() {}
public function bar() {}
public function baz() {}
}
?>
Пример #6 Интерфейсы с константами
<?php
interface A
{
const B = 'Константа интерфейса';
}
// Выведет: Константа интерфейса
echo A::B;
class B implements A
{
const B = 'Константа класса';
}
// Выведет: Константа класса
// До PHP 8.1.0 этот код не будет работать,
// поскольку не разрешалось переопределять константы
echo B::B;
?>
Пример #7 Интерфейсы с абстрактными классами
<?php
interface A
{
public function foo(string $s): string;
public function bar(int $i): int;
}
// Абстрактному классу можно реализовывать только часть интерфейса.
// Классы, которыми расширяется абстрактный класс, должны реализовать остальные требования интерфейса
abstract class B implements A
{
public function foo(string $s): string
{
return $s . PHP_EOL;
}
}
class C extends B
{
public function bar(int $i): int
{
return $i * 2;
}
}
?>
Пример #8 Одновременное расширение класса и реализация интерфейсов
<?php
class One
{
/* ... */
}
interface Usable
{
/* ... */
}
interface Updatable
{
/* ... */
}
// Порядок ключевых слов здесь важен. Слово extends должно идти первым
class Two extends One implements
Usable,
Updatable
{
/* ... */
}
?>
Интерфейс вместе с объявлениями типов предоставляет надёжный
способ проверки того, что конкретный объект содержит конкретные
методы. Смотрите также описание оператора
instanceof
и раздел «Объявления типов».