PHP 8.4.2 Released!

Интерфейсы объектов

Интерфейсы объектов разрешают создавать код, который указывает, какие методы и свойства должен реализовать класс, без определения реализации этих методов или свойств. Интерфейсы разделяют пространство имён с классами, трейтами и перечислениями, поэтому они не могут называться одинаково.

Интерфейсы определяются так же, как классы, но с ключевым словом 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 и раздел «Объявления типов».

Добавить

Примечания пользователей 4 notes

up
32
thanhn2001 at gmail dot com
13 years ago
PHP prevents interface a contant to be overridden by a class/interface that DIRECTLY inherits it. However, further inheritance allows it. That means that interface constants are not final as mentioned in a previous comment. Is this a bug or a feature?

<?php

interface a
{
const
b = 'Interface constant';
}

// Prints: Interface constant
echo a::b;

class
b implements a
{
}

// This works!!!
class c extends b
{
const
b = 'Class constant';
}

echo
c::b;
?>
up
18
vcnbianchi
2 years ago
Just as all interface methods are public, all interface methods are abstract as well.
up
7
williebegoode at att dot net
10 years ago
In their book on Design Patterns, Erich Gamma and his associates (AKA: "The Gang of Four") use the term "interface" and "abstract class" interchangeably. In working with PHP and design patterns, the interface, while clearly a "contract" of what to include in an implementation is also a helpful guide for both re-use and making changes. As long as the implemented changes follow the interface (whether it is an interface or abstract class with abstract methods), large complex programs can be safely updated without having to re-code an entire program or module.

In PHP coding with object interfaces (as a keyword) and "interfaces" in the more general context of use that includes both object interfaces and abstract classes, the purpose of "loose binding" (loosely bound objects) for ease of change and re-use is a helpful way to think about both uses of the term "interface." The focus shifts from "contractual" to "loose binding" for the purpose of cooperative development and re-use.
up
1
xedin dot unknown at gmail dot com
3 years ago
This page says that if extending multiple interfaces with the same methods, the signature must be compatible. But this is not all there is to it: the order of `extends` matters. This is a known issue, and while it is disputable whether or not it is a bug, one should be aware of it, and code interfaces with this in mind.

https://bugs.php.net/bug.php?id=67270
https://bugs.php.net/bug.php?id=76361
https://bugs.php.net/bug.php?id=80785
To Top