PHP 8.0: Типы данных (2/4)
Вышла версия PHP 8.0. Она настолько насыщена новинками, как ни одна версия до этого. Их представление потребовало целых четыре отдельных статьи. В этой второй мы рассмотрим типы данных.

Вернемся в историю. Существенным прорывом
PHP 7 стало введение скалярных type hint. Этого
могло и не случиться. Сообщество грубо
отвергло автора замечательного решения Андреа Фаулдс, которое
благодаря declare(strict_types=1)
было
полностью обратно совместимым и
необязательным. К счастью, тогда за нее и ее
предложение заступился Энтони Феррара,
запустил кампанию, и RFC с трудом прошел. Уфф.
Большинство новинок в PHP 8 — заслуга
легендарного Никиты
Попова, и в голосовании они прошли как по
маслу. Мир меняется к лучшему.
PHP 8 доводит типы до совершенства.
Исчезнет абсолютное большинство phpDoc
аннотаций @param
, @return
и
@var
в коде, их заменит нативная запись
и, главное, проверка движком PHP. В
комментариях останутся только описания
структур вроде string[]
или более
сложные аннотации для PHPStan.
Union Types
Union типы — это перечисление двух или более типов, которые может принимать значение:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
Некоторые специальные union типы PHP знал и
раньше. Например, nullable типы, такие как
?string
, что является эквивалентом union
типа string|null
, и вопросительную запись
можно считать лишь сокращением. Разумеется,
она работает и в PHP 8, но ее нельзя
комбинировать с вертикальными чертами, то
есть вместо ?string|object
нужно писать
полное string|object|null
. Далее, iterable
всегда был эквивалентом array|Traversable
.
Возможно, вас удивит, что union типом
фактически является и float
, который на
самом деле принимает int|float
, однако
преобразует в float
.
В union типах нельзя использовать
псевдотипы void
и mixed
, так как
это не имело бы никакого смысла.
Nette готов к union типам. В Schema с ними разбирается Expect::from(), разбираются с ними и презентеры, так что вы можете использовать их, например, в методах render и action:
public function renderDetail(int|array $id)
{
...
}
Напротив, автовайринг в Nette DI union типы отвергает. Пока отсутствует use case, где имело бы смысл, чтобы, например, конструктор принимал либо тот, либо иной объект. Конечно, если он появится, можно будет соответственно изменить поведение контейнера.
Методы getParameterType()
, getReturnType()
и
getPropertyType()
в Nette\Utils\Reflection выбрасывают
исключение в случае union типа (в версии 3.1, в
старой 3.0 возвращают null из-за
совместимости).
mixed
Псевдотип mixed
говорит, что значение
может быть абсолютно любым.
В случае параметров и свойств это
фактически то же самое поведение, как если
бы мы не указывали никакого типа. Зачем он
тогда нужен? Чтобы можно было различить,
когда тип просто отсутствует, а когда он
действительно mixed
.
В случае возвращаемого значения функции и
метода неуказание типа отличается от
указания типа mixed
. Это фактически
противоположность void
, поскольку
говорит, что функция должна что-то вернуть.
Отсутствующий return тогда является фатальной
ошибкой.
На практике его следует использовать редко, потому что благодаря union типам вы можете точнее указать значение. Он подходит для исключительных ситуаций:
function dump(mixed $var): mixed
{
// вывести переменную
return $var;
}
false
Новый псевдотип false
можно
использовать только в union типах. Он возник
из-за необходимости нативно описать тип
возвращаемого значения у нативных функций,
которые исторически в случае неудачи
возвращают false:
function strpos(string $haystack, string $needle): int|false
{
}
По этой причине не существует типа
true
, нельзя использовать и само
false
, или false|null
, или
bool|false
.
static
Псевдотип static
можно использовать
только как возвращаемый тип метода. Он
говорит, что метод возвращает объект того
же типа, что и сам объект (в то время как
self
говорит, что возвращает класс, в
котором определен метод). Что отлично
подходит для описания fluent interfaces:
class Item
{
public function setValue($val): static
{
$this->value = $val;
return $this;
}
}
class ItemChild extends Item
{
public function childMethod()
{
}
}
$child = new ItemChild;
$child->setValue(10)
->childMethod();
resource
Этого типа в PHP 8 не существует и в будущем
он введен не будет. Ресурсы (Resources) — это
исторический реликт времен, когда в PHP еще
не было объектов. Постепенно ресурсы будут
заменяться объектами, и со временем этот
тип полностью исчезнет. Например, PHP
8.0 заменяет ресурс, представляющий
изображение, объектом GdImage
, а ресурс
соединения curl
— объектом
CurlHandle
.
Stringable
Это интерфейс, который автоматически
реализует каждый объект с магическим
методом __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
В определении класса можно явно указать
class Email implements Stringable
, но это не
обязательно.
Этот стиль именования отражает и
Nette\Utils\Html
, который реализует
интерфейс Nette\HtmlStringable
вместо
предыдущего IHtmlString
. Объекты этого
типа, например, Latte не экранирует.
Вариантность типов, контравариантность, ковариантность
Принцип подстановки Лисков (Liskov Substitution Principle – LSP) гласит, что потомки класса и реализации интерфейса никогда не должны требовать больше и предоставлять меньше, чем родитель. То есть метод потомка не должен требовать больше аргументов или принимать для параметров более узкий диапазон типов, чем предок, и наоборот, не должен возвращать более широкий диапазон типов. Но может возвращать более узкий. Почему? Потому что иначе наследование вообще бы не работало. Функция хоть и приняла бы объект определенного типа, но не знала бы, какие параметры можно передавать методам и что они будут на самом деле возвращать, потому что любой потомок мог бы это нарушить.
Таким образом, в ООП действует правило, что потомок может:
- в параметрах принимать более широкий диапазон типов (это называется контравариантностью)
- возвращать более узкий диапазон типов (ковариантность)
- а свойства (properties) не могут изменять тип (они инвариантны)
PHP это умеет с версии 7.4, и все вновь введенные типы в PHP 8.0 также поддерживают контравариантность и ковариантность.
В случае mixed
потомок может сузить
возвращаемое значение до любого типа, но не
void
, потому что это не тип значения, а
его отсутствие. Также потомок не может не
указывать тип, потому что это также
допускает отсутствие.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Также union типы можно в параметрах расширять, а в возвращаемых значениях сужать:
class A
{
public function foo(string|int $foo): string|int
{}
}
class B extends A
{
public function foo(string|int|float $foo): string
{}
}
Далее, false
может быть в параметре
расширено до bool
, или наоборот,
bool
в возвращаемом значении сужено до
false
.
Все нарушения ковариантности/контравариантности приводят в PHP 8.0 к фатальной ошибке.
В следующих частях мы покажем, что такое атрибуты, какие новые функции и классы появились в PHP, и представим Just in Time Compiler.
Чтобы оставить комментарий, пожалуйста, войдите в систему