PHP 8.0: Типи даних (2/4)
Вийшла версія PHP 8.0. Вона настільки насичена новинками, як жодна версія до неї. Їх представлення вимагало аж чотирьох окремих статей. У цій другій ми розглянемо типи даних.

Повернемося до історії. Суттєвим проривом
PHP 7 стало введення скалярних підказок
типів. Майже до цього не дійшло. Авторку
чудового рішення Andreu
Faulds, яке завдяки declare(strict_types=1)
було
повністю зворотно сумісним і
необов'язковим, спільнота грубо відкинула.
На щастя, за неї та її пропозицію тоді
заступився Anthony Ferrara,
розпочав кампанію, і RFC дуже тісно пройшов.
Уффф. Більшість новинок у PHP 8 належить
легендарному Nikita Popov, і
в голосуванні вони пройшли як по маслу. Світ
змінюється на краще.
PHP 8 доводить типи до досконалості. Зникне
абсолютна більшість phpDoc анотацій
@param
, @return
та @var
у коді,
їх замінить нативний запис і, головне,
перевірка рушієм PHP. У коментарях
залишаться лише описи структур, таких як
string[]
, або складніші анотації для
PHPStan.
Типи Union
Типи 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 готова до роботи з об'єднаними типами.
У Schema Expect::from()
приймає їх, і презентатори також приймають
їх. Ви можете використовувати їх, наприклад,
у методах рендерингу та дії:
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 і в майбутньому
вводитися не буде. Ресурси є історичним
реліктом з часів, коли 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) говорить, що нащадки класу та реалізації інтерфейсу ніколи не повинні вимагати більше і надавати менше, ніж батько. Тобто, метод нащадка не повинен вимагати більше аргументів або приймати для параметрів вужчий діапазон типів, ніж предок, і навпаки, не повинен повертати ширший діапазон типів. Але може повертати вужчий. Чому? Тому що інакше успадкування взагалі б не працювало. Функція б хоч і прийняла об'єкт певного типу, але не знала б, які параметри можна передавати методам і що вони насправді повертатимуть, оскільки будь-який нащадок міг би це порушити.
Отже, в ООП діє правило, що нащадок може:
- у параметрах приймати ширший діапазон типів (це називається контраваріантністю)
- повертати вужчий діапазон типів (коваріантність)
- а властивості не можуть змінювати тип (вони інваріантні)
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.
Щоб залишити коментар, будь ласка, увійдіть до системи