Поиск по сайту:

Как новые типы пересечений в PHP 8.1 дают вам больше гибкости


Типы пересечения — это новая функция системы типов, появившаяся в PHP 8.1. Они позволяют вам вводить значения, которые должны удовлетворять более чем одному ограничению типа. В PHP уже есть типы объединения, которые объединяют типы с логическим предложением «или»; Типы пересечения предлагают предложение «и» вместо этого.

Некоторые разработчики уже указывают пересечения с помощью аннотаций PHPDoc. Добавление встроенной поддержки — это шаг вперед, поскольку типы фактически будут применяться во время выполнения. Они также полностью совместимы с наследованием классов, что помогает снизить риск ошибок из-за устаревших или несовместимых аннотаций.

Базовый синтаксис

Синтаксис типа пересечения аналогичен типам объединения:

class DemoClass {
 
    public Countable|Stringable $Union;
 
    public Countable&Stringable $Intersection;
 
}

В этом примере $Union будет соответствовать любому значению, которое является либо Countable, либо Stringable. $Intersection будет принимать только те значения, которые соответствуют обоим ограничениям. Вы получите TypeError, если присвоите свойству значение, реализующее один или ноль подсказанных типов.

Типы пересечения поддерживаются везде, где работают подсказки типов. Вы можете использовать их с определениями свойств, параметрами функций и возвращаемыми значениями.

В отличие от типов объединения, пересечения могут указывать только интерфейсы и классы. Скалярное пересечение, такое как int&string, бессмысленно, поскольку оно никогда не может быть удовлетворено. Тип mixed также запрещен, потому что каждое значение будет удовлетворять его ограничению.

Тип дисперсии

Типы пересечений соответствуют существующим правилам отклонения PHP. Типы возвращаемых значений переопределенных методов должны быть ковариантными, а типы параметров — контравариантными. Свойства класса всегда инвариантны, поэтому дочерние элементы не могут изменить определение типа, если только новая сигнатура не является одновременно подтипом и надтипом унаследованной.

Для возвращаемых значений правила отклонения означают, что вы можете добавлять дополнительные типы пересечений в переопределенные методы. Методы также могут удалять, но не добавлять типы к пересечениям, используемым в качестве параметров. Эти правила обеспечивают соблюдение принципа замещения Лискова.

interface I1 {
    public function method1(A&B $demo) : X&Y;
    public function method2(A&B $demo) : X&Y;
}
 
interface I2 extends I1 {
 
    // ALLOWED
    public function method1(A $demo) : X&Y&Z;
 
    // NOT ALLOWED
    public function method2(A&B&C $demo) : X;
 
}

В случае method1 ограничения фактически не изменились. Вы утверждаете, что переопределенный метод может работать с любым A, даже если это не X. Он менее специфичен, что приводит к приемлемой дисперсии параметров. Объявление возврата является более конкретным, заявляя, что значение будет реализовывать X, Y и Z; в этом сценарии добавление специфичности не нарушает контракт, поскольку значение по-прежнему будет приниматься всем, что указывает тип I1.

Переопределение method2 не работает в обоих случаях. Требуя, чтобы входной параметр удовлетворял требованиям A, B и C, он больше не является заменой для I1. . Точно так же method2 только гарантирует, что возвращаемое значение будет экземпляром X. Это нарушает контракт, так как любой тип подсказки I1 требует, чтобы значение удовлетворяло пересечению X и Y.

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

Как и в случае типов объединения, правила отклонения также применяются к типам, составляющим пересечение — это отдельные части X и Y X&Y. Вы можете сузить тип при его возврате, заявив, что вернете подкласс, или расширить его в качестве параметра, приняв суперкласс.

Пересечения также имеют некоторые специальные правила, касающиеся псевдонимов и конкретных реализаций. Если вы вводите X&Y, но пишете интерфейс Z extends X, Y, логично, что Z удовлетворяет ограничению. Следовательно, вы можете ввести Z вместо X&Y везде, где разрешена ковариация:

interface Test extends X, Y {}
 
interface Demo1 {
    public function demo() : X&Y;
}
 
interface Demo2 {
    public function demo() : Test;
}

Это позволяет вам ввести конкретный класс или интерфейс, если вы зависите от дополнительных функций. Это приемлемо до тех пор, пока все ограничения в пересечении удовлетворяются итоговой подсказкой типа.

Когда использовать типы пересечения?

Типы пересечения предназначены для тех случаев, когда вы хотите убедиться, что значение удовлетворяет составному интерфейсу, не определяя этот интерфейс на самом деле. Предыдущие версии PHP не предоставляли для этого никакой встроенной поддержки, поэтому вам нужно было добавить кучу дополнительных шаблонов в свою кодовую базу:

interface CountableStringable extends Countable, Stringable {
    // ...
}
 
class FakeArray implements CountableStringable {
 
    public array $items = [];
 
    public function count() : int {
        return count($this -> items);
    }
 
    public function __toString() : string {
        return implode(", ", $this -> items);
    }
 
}
 
class DemoClass {
 
    public CountableStringable $Value;
 
}

Это приводит к избытку неглубоких интерфейсов, похожих на заглушки, для достижения базового поведения пересечения. Хотя до сих пор PHP-разработчики выживали без пересечений, их присутствие помогает укрепить составные возможности системы типов. Использование нативных пересечений создает более чистый и интуитивно понятный код.

Как насчет составных типов?

Невозможно комбинировать типы пересечения и объединения в одном и том же typehint. Хотя это было бы технически возможно, оно было исключено из текущего RFC, поскольку существуют неясности в отношении синтаксиса, приоритета и дисперсии.

Составные типы остаются идеей будущего. Если бы они были добавлены, вы могли бы начать добавлять сложные подсказки типов, например:

class DemoClass {
 
    public Countable&Stringable|CountableStringable $Intersection;
 
}

Этот пример класса сочетает в себе две реализации из приведенного выше. Если бы это сработало, это позволило бы вам использовать нативные типы пересечений, сохраняя при этом обратную совместимость со старыми классами, использующими «фальшивый» подход.

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

Краткое содержание

Типы пересечений появятся в PHP 8.1 и откроют более продвинутые возможности в системе типов. Расширение параметров составных типов уменьшает объем кода, который необходимо написать, когда поддерживается несколько интерфейсов.

Пересечения — это необязательная функция, которая не приведет к несовместимости с существующим кодом. RFC был реализован в третьей альфа-сборке PHP 8.1. Финальный релиз выйдет в конце ноября этого года.




Все права защищены. © Linux-Console.net • 2019-2024