Интерфейсы объектов
Интерфейсы объектов позволяют создавать код, который
указывает, какие методы должен реализовать класс, без
необходимости определять, как именно они должны быть реализованы.
Интерфейсы разделяют пространство имён с классами и трейтами,
поэтому они не могут называться одинаково.
Интерфейсы объявляются так же, как и обычные классы, но с использованием
ключевого слова interface
вместо class
.
Тела методов интерфейсов должны быть пустыми.
Все методы, определённые в интерфейсах должны быть общедоступными, что
следует из самой природы интерфейса.
На практике интерфейсы используются в двух взаимодополняющих случаях:
-
Чтобы позволить разработчикам создавать объекты разных классов, которые могут использоваться взаимозаменяемо,
поскольку они реализуют один и тот же интерфейс или интерфейсы.
Типичный пример - несколько служб доступа к базе данных, несколько платёжных шлюзов или разных стратегий кеширования.
Различные реализации могут быть заменены, без каких-либо изменений в коде, который их использует.
-
Чтобы разрешить функции или методу принимать и оперировать параметром, который соответствует
интерфейсу, не заботясь о том, что ещё может делать объект или как он реализован.
Эти интерфейсы часто называют
Iterable
, Cacheable
, Renderable
и так далее, чтобы описать их поведение.
Интерфейсы могут определять
магические методы, требующие реализации классов для реализации этих методов.
Замечание:
Хотя они поддерживаются, использование конструкторов
в интерфейсах настоятельно не рекомендуется. Это значительно снижает гибкость объекта, реализующего интерфейс.
Кроме того, к конструкторам не применяются правила наследования, что может привести к противоречивому
и неожиданному поведению.
implements
Для реализации интерфейса используется оператор implements
.
Класс должен реализовать все методы, описанные в интерфейсе, иначе
произойдёт фатальная ошибка. При желании классы могут реализовывать
более одного интерфейса, разделяя каждый интерфейс запятой.
Внимание
Класс может реализовать два интерфейса, которые определяют метод с тем же именем,
только если объявление метода в обоих интерфейсах идентично.
Внимание
Класс, реализующий интерфейс, может использовать для своих параметров имя, отличное
от имени интерфейса. Однако, начиная с PHP 8.0, в языке поддерживаются
именованные аргументы и вызывающие абоненты могут полагаться на имя параметра в интерфейсе.
По этой причине настоятельно рекомендуется, чтобы разработчики использовали те же имена параметров, что и реализуемый интерфейс.
Замечание:
Интерфейсы могут быть унаследованы друг от друга, так же, как и классы,
с помощью оператора extends.
Замечание:
Класс, реализующий интерфейс, должен объявить все методы в интерфейсе с
совместимой сигнатурой.
Константы (Constants
)
Интерфейсы могут содержать константы. Константы интерфейсов
работают точно так же, как и
константы классов, за исключением того, что они не могут
быть переопределены наследующим классом или интерфейсом.
Примеры
Пример #1 Пример интерфейса
<?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;
}
}
?>
Пример #2 Наследование интерфейсов
<?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)
{
}
}
?>
Пример #3 Множественное наследование интерфейсов
<?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()
{
}
}
?>
Пример #4 Интерфейсы с константами
<?php
interface A
{
const B = 'Константа интерфейса';
}
// Выведет: Константа интерфейса
echo A::B;
// Это, однако, не будет работать, так как
// константы переопределять нельзя.
class B implements A
{
const B = 'Константа класса';
}
?>
Пример #5 Интерфейсы с абстрактными классами
<?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;
}
}
?>
Пример #6 Одновременное расширение и внедрение
<?php
class One
{
/* ... */
}
interface Usable
{
/* ... */
}
interface Updatable
{
/* ... */
}
// Порядок ключевых слов здесь важен. "extends" должно быть первым.
class Two extends One implements Usable, Updatable
{
/* ... */
}
?>
Интерфейс, совместно с объявлениями типов, предоставляет отличный
способ проверки того, что определённый объект содержит определённый
набор методов. Смотрите также оператор
instanceof и
объявление типов.