PHP 8.0: Новости в типах данных (2/4)
PHP версии 8.0 только что был выпущен. Она полна новых возможностей, как ни одна из предыдущих версий. Их введение заслуживает четырех отдельных статей. Во второй части мы рассмотрим типы данных.
Давайте вернемся в историю. Введение
подсказок скалярных типов было
значительным прорывом в PHP 7. Этого почти не
произошло. Андреу Фолдс,
автор гениального решения, которое было
полностью обратно совместимым и
необязательным благодаря
declare(strict_types=1)
, был жестко отвергнут
сообществом. К счастью, Энтони Феррара
защитил ее и предложение в то время,
запустил кампанию, и RFC прошел очень близко.
Фух. Большинство новостей в PHP 8 – заслуга
легендарного Никиты
Попова, и все они прошли голосование как
нож сквозь масло. Мир меняется к лучшему.
PHP 8 доводит типы до совершенства.
Подавляющее большинство аннотаций phpDoc,
таких как @param
, @return
и
@var
, будут заменены на родную нотацию.
Но самое главное, типы будут проверяться
движком PHP. В комментариях останутся только
описания структур, таких как string[]
или более сложные аннотации для PHPStan.
Типы союзов
Союзные типы – это перечисление двух или более типов, которые может принимать переменная:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
Некоторые союзные типы уже были
представлены в PHP ранее. Например, нулевые
типы. Например, ?string
, который
эквивалентен типу объединения
string|null
. Нотацию вопросительного
знака можно считать аббревиатурой. Конечно,
оно работает и в PHP 8, но вы не можете
сочетать его с вертикальными полосами.
Поэтому вместо ?string|object
вы должны
написать string|object|null
. Более того,
iterable
всегда был эквивалентен
array|Traversable
. Вас может удивить, что
float
также является союзным типом,
поскольку он принимает и int|float
, но
приводит значение к float
.
Вы не можете использовать псевдотипы
void
и mixed
в союзах, потому что
это не имеет смысла.
Nette готов к использованию типов союзов. В
Schema, Expect::from()
принимает их, и presenters также принимает их. Вы
можете использовать их, например, в методах
render и action:
public function renderDetail(int|array $id)
{
...
}
С другой стороны, autowiring в Nette DI отвергает типы union. Пока что не существует случая, когда конструктору имело бы смысл принимать либо один, либо другой объект. Конечно, если такой случай появится, можно будет соответствующим образом скорректировать поведение контейнера.
Методы getParameterType()
, getReturnType()
и
getPropertyType()
в Nette\Utils\Reflection
выбрасывают исключение в случае типа union
(это в версии 3.1; в более ранней версии 3.0 эти
методы действительно возвращаются для
поддержания совместимости с null).
mixed
Псевдотип mixed
принимает любое
значение.
В случае параметров и свойств это
приводит к такому же поведению, как если бы
мы не указывали никакого типа. Итак, каково
его назначение? Чтобы различать, когда тип
просто отсутствует, а когда он намеренно
mixed
.
В случае с возвращаемыми значениями
функций и методов неуказание типа
отличается от использования mixed
. Это
означает противоположное void
, так как
требует, чтобы функция что-то возвращала.
Отсутствие возврата приводит к фатальной
ошибке.
На практике его следует использовать редко, поскольку благодаря типам объединения можно точно указать значение. Поэтому он подходит только в уникальных ситуациях:
function dump(mixed $var): mixed
{
// print variable
return $var;
}
false
В отличие от mixed
, новый псевдотип
false
можно использовать
исключительно в союзных типах. Он возник в
связи с необходимостью описания
возвращаемого типа нативных функций,
которые исторически возвращают false в случае
неудачи:
function strpos(string $haystack, string $needle): int|false
{
}
Поэтому типа true
не существует. Вы
также не можете использовать ни false
,
ни false|null
, ни bool|false
.
static
Псевдотип static
можно использовать
только в качестве возвращаемого типа
метода. Он говорит, что метод возвращает
объект того же типа, что и сам объект (в то
время как self
говорит, что
возвращается класс, в котором определен
метод). Он отлично подходит для описания
беглых интерфейсов:
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 и не будет
представлен в будущем. Ресурсы – это
пережиток того времени, когда в PHP не было
объектов. Постепенно ресурсы будут
заменяться объектами. В конце концов, они
исчезнут полностью. Например, в PHP 8.0 ресурс
image заменен объектом 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.
Типовая дисперсия, контравариант, ковариация
Принцип замещения Лискова (LSP) гласит, что классы расширения и реализации интерфейсов никогда не должны требовать больше и предоставлять меньше, чем родитель. То есть дочерний метод не должен требовать больше аргументов или принимать более узкий диапазон типов для параметров, чем родительский, и наоборот, он не должен возвращать более широкий диапазон типов. Но он может возвращать меньше. Почему? Потому что в противном случае наследование нарушится. Функция будет принимать объект определенного типа, но она не будет иметь представления о том, какие параметры она может передавать своим методам и какие типы они будут возвращать. Любой ребенок может сломать ее.
Поэтому в ООП дочерний класс может:
- принимать более широкий диапазон типов в параметрах (это называется контравариант)
- возвращать более узкий диапазон типов (ковариация)
- а свойства не могут менять тип (они инвариантны).
PHP умеет это делать с версии 7.4, и все новые типы, появившиеся в PHP 8.0, также поддерживают contravariance и covariance.
В случае с mixed
ребенок может сузить
возвращаемое значение до любого типа, но не
void
, так как он не представляет
значение, а скорее его отсутствие. Ребенок
также не может опустить объявление типа,
поскольку это также допускает отсутствие
значения.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Союзные типы также могут быть расширены в параметрах и сужены в возвращаемых значениях:
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..
Чтобы оставить комментарий, пожалуйста, войдите в систему