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

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

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

`↑` and `↓` to navigate • `Enter` to select • `Esc` to close • `/` to open