PHP прошел долгий путь в работе с типами. Теперь у нас есть скалярные типы, возвращаемые типы, nullable-типы и даже типы свойств в PHP 7.4!
PHP 8.0 поставляется с поддержкой объединений типов (Union Types)!
В версиях до PHP 8.0 вы могли объявлять только один тип для свойств, параметров и возвращаемых значений. В PHP 7.1 и более новых версиях появились nullable-типы, что означает возможность объявить тип как null с помощью объявления, похожего на ?string .
Начиная с PHP 8.0, вы можете объявлять более одного типа для аргументов, возвращаемых значений и свойств классов.
class Example {
private int|float $foo;
public function squareAndAdd(float|int $bar): int|float {
return $bar ** 2 + $foo;
}
} Аналогично типам, применявшимся в предыдущих версиях PHP, теперь PHP будет следить за тем, чтобы параметры функций, возвращаемые типы и свойства классов соответствовали одному из типов, объявленных в определении.
Объединения типов до PHP 8.0
Nullable-типы
В PHP уже есть поддержка nullable-типов; вы можете объявить тип как nullable, добавив префикс ? к типу. Например, если определенное свойство может быть string или null , вы можете объявить его как ?string . С появлением объединений типов в PHP 8.0 запись string|null будет функционально эквивалентна ?string .
Тип iterable
Также существует псевдотип iterable , который функционально эквивалентен array|Traversable .
Комментарии PHPDoc
Стандарт PHPDoc поддерживал объединения типов. Вы могли использовать комментарий PHPDoc для объявления типов, разрешенных для определенного блока кода:
class Example {
/**
* @var int|float
*/
private $foo;
/**
* @param int|float $bar
* @return int|float
*/
public function squareAndAdd(int $bar): int {
return $bar ** 2 + $this->foo;
}
} Объединения типов в PHP 8.0
Начиная с PHP 8.0, вы можете объявлять любое количество произвольных типов для свойств, аргументов и возвращаемых типов. Есть несколько исключений, которые не имеют смысла для совместного использования, например string|void .
Теперь вы можете полностью избавиться от комментариев PHPDoc @var , @param и @return в пользу типов, контролируемых непосредственно в самом коде. Это определенно поможет очистить шаблонные комментарии, которые присутствовали исключительно потому, что их нельзя было объявить в коде ранее.
Существующие nullable-типы не удаляются и не объявляются устаревшими. Вы можете продолжать использовать ?string как сокращение для string|null .
Тип iterable также не будет удален и будет функционально равен array|Traversable .
В RFC подробно объясняются некоторые ограничения и другие улучшения объединений типов:
Тип void не разрешен в объединении
В PHP уже есть поддержка типа void . Он разрешен только в качестве возвращаемого типа, так как использование void в любом другом месте не имело бы смысла. Функция либо не должна возвращать никакого значения, либо должна вызывать return; без указания значения. В объединениях типов PHP 8.0 тип void не разрешается комбинировать с любым другим типом.
Например, следующее не допускается:
function foo(): void|null {} Специальный тип false
Ядро PHP и многие устаревшие библиотеки возвращают return false для указания отрицательного результата.
Например, если у вас есть функция, которая загружает учетную запись пользователя по ее ID, она может возвращать return false , чтобы указать, что пользователь не существует.
Чтобы упростить внедрение объединений типов, разрешено использовать false как часть объединения типов.
function user_load(int $id): User|false {
} Приведенный выше фрагмент имитирует функцию user_load() в Drupal, где она возвращает объект User , если учетная запись пользователя найдена, или false в противном случае. Этот специальный тип false может быть очень полезен в такой ситуации.
Существует также несколько функций ядра PHP, которые следуют этому же шаблону. Например, функция strpos() возвращает int с позицией, где байты искомой строки были найдены в строке, или false , если они не найдены. Специальный тип false здесь тоже может пригодиться.
- false нельзя использовать как отдельный тип. Это означает, что объявление типа вроде public false $foo не допускается. Для этого вы все еще можете использовать тип bool . Начиная с PHP 8.2, использование false в качестве отдельного типа разрешено.
- До PHP 8.2 не существует псевдотипа true . Цель псевдотипа false — приспособиться к (в основном устаревшему) коду, который возвращает false в случае ошибки. Используйте там тип bool . В PHP 8.2 вводится true в качестве типа, но его нельзя использовать в объединении с false , то есть false|true не допускается, и вместо этого нужно использовать bool .
- Псевдотип false разрешен везде, где разрешены типы: свойства классов, аргументы функций и возвращаемые типы поддерживают тип false .
- *Если используется bool , то false не может быть использован в том же объявлении типа.
Изменения в PHP 8.2 В семантику, описанную выше, внесены некоторые изменения:
Nullable-типы ( ?TYPE ) и null нельзя смешивать.
Хотя ?string является сокращением для string|null , эти две нотации нельзя смешивать.
Если ваше объявление типа содержит более одного типа и null, оно должно быть объявлено следующим образом:
TYPE_1|TYPE_2|null
Оно не должно объявляться как ?TYPE_1|TYPE_2 , так как это было бы неоднозначным объявлением.
Проверка ошибок на этапе компиляции
Объединения типов не допускают дублирующихся или избыточных типов в объявлении. Эта проверка будет происходить на этапе компиляции без автозагрузки классов/интерфейсов.
Дублирующиеся типы не допускаются.
Вы не можете объявить int|int или int|INT , так как по сути это одно и то же. Более того, int|?int также не допускается. Последнее приведет к синтаксической ошибке, а первое вызовет следующую ошибку:
Fatal error: Duplicate type ... is redundant in ... on line ...
Избыточные типы не допускаются.
Объединения типов не допускают избыточных типов классов. Однако важно отметить, что это относится только к нескольким специальным типам.
- bool|false не допускается, потому что false является типом bool .
- `object` нельзя использовать с именем класса, потому что все объекты классов также относятся к типу object .
- iterable нельзя использовать с array или Traversable , потому что iterable уже является объединением типов array|Traversable .
- Имена классов можно использовать, даже если один наследует другой.
Поскольку объявления объединений типов проверяются на этапе компиляции, ошибки не будут возникать, если вы используете родительский и дочерний классы в объединении, так как для этого потребовалось бы разрешение иерархии классов.
Вот несколько примеров:
function foo(): bool|false {} // Fatal error: Duplicate type false is redundant in ... on line ...
function foo(): DateTime|object {} // Fatal error: Type DateTime|object contains both object and a class type, which is redundant in ... on line ...
function foo(): iterable|array {} // Fatal error: Type iterable|array contains both iterable and array, which is redundant in ... on line ... Поскольку объявление типа проверяется только на этапе компиляции, следующее является допустимым:
class A{}
class B extends A{}
function foo(): A|B {} Вариантность типов
Вариантность объединений типов соответствует принципу LSP. Это гарантирует, что все подклассы и реализации интерфейса не должны изменять поведение программы и по-прежнему соответствовать контракту. Это соблюдается в PHP даже без объединений типов.
- Параметры контрвариантны: типы могут быть расширены супертипом.
- Возвращаемые типы ковариантны: типы могут быть сужены до подтипа.
- Типы свойств инвариантны: типы не могут быть изменены на подтип или супертип.
Добавление или удаление типов в объединение
class A{
public function foo(string|int $foo): string|int {}
}
class B extends A{
public function foo(string|int|float $foo): string {}
} В приведенном выше фрагменте типы параметров расширены (с добавлением типа float ). Это разрешено, поскольку все программы, использующие класс B , ожидают, что они будут принимать все типы, которые принимает класс A . Класс B по-прежнему выполняет этот контракт.
Возвращаемый тип в B::foo сужен еще сильнее, но это все равно соответствует LSP, поскольку класс B соответствует возвращаемому типу, объявленному в A::foo .
Если бы вы изменили тип таким образом, что класс B перестал бы выполнять контракт, это вызвало бы фатальную ошибку:
class A{
public function foo(string|int $foo): string|int {}
}
class B extends A{
public function foo(string|float $foo): string|int {}
} Fatal error: Declaration of B::foo(string|float $foo): string|int must be compatible with A::foo(string|int $foo): string|int in ... on line ... Вариантность отдельных типов в объединении
Те же правила вариантности соблюдаются при изменении отдельных членов объединения типов.
Подклассы
class ParentClass {}
class ChildClass extends ParentClass{}
class FooParent {
public function foo(string|ChildClass $a): string {}
public function bar(string $b): string:ParentClass {}
}
class FooChild extends FooParent{
public function foo(string|ParentClass $a): string|ChildClass {}
public function bar(string $b): string:ChildClass {}
} Это абсолютно корректно, так как в функциях FooChild::foo параметры расширяются. Весь существующий код, работающий с классом FooParent , будет работать, потому что ChildClass является подтипом ParentClass с той же функциональностью.
В функции FooChild::bar возвращаемый тип сужен еще сильнее. Это также допустимо, поскольку FooChild::bar по-прежнему выполняет свой контракт, возвращая подтип string или ChildClass , который наследует ParentClass .
Nullable
class FooParent {
public function foo(string $a): int|null {}
}
class FooChild extends FooParent{
public function foo(string|null $b): int|null {}
} Возвращаемым типам разрешено терять тип nullable , а параметрам разрешено делать так, чтобы они принимали null .
bool и false
В объединении типов PHP считает false подтипом для bool . Это позволяет использовать такую вариантность:
class FooParent {
public function foo(int|false $a): int|bool {}
}
class FooChild extends FooParent{
public function foo(int|bool $b): int|false {}
} Влияние на обратную совместимость
Объединения типов — это новая функция в PHP, и ее новый синтаксис не нарушает существующую функциональность. Поскольку это синтаксическое изменение в движке, эту функциональность нельзя перенести в более старые версии с помощью полифила.
Комментарии (0)
Пока нет комментариев — будьте первым.