PHP 8.0: Новини про типи даних (2/4)
Щойно вийшла версія PHP 8.0. Вона сповнена нових можливостей, як жодна попередня версія. Їх представлення заслуговує на чотири окремі статті. У другій частині ми розглянемо типи даних.
Давайте повернемося в історію. Введення
підказок скалярних типів було значним
проривом в PHP 7. Цього майже не сталося. Andreu Faulds, автор геніального
рішення, яке було повністю зворотньо
сумісним і необов'язковим завдяки
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()
приймає їх, і презентатори також приймають
їх. Ви можете використовувати їх, наприклад,
у методах рендерингу та дії:
public function renderDetail(int|array $id)
{
...
}
З іншого боку, автопідключення у Nette DI відкидає типи об'єднань. Поки що не існує жодного варіанту використання, в якому було б доцільно, щоб конструктор приймав той чи інший об'єкт. Звісно, якщо такий випадок з'явиться, можна буде відповідним чином налаштувати поведінку контейнера.
Методи getParameterType()
, getReturnType()
та getPropertyType()
у Nette\Utils\Reflection
генерують виключення у випадку типу
об'єднання (це у версії 3.1; у попередній
версії 3.0 ці методи повертають нульове
значення для збереження сумісності).
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.
Тип variance, contravariance, covariance
Принцип підстановки Ліскова (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
{}
}
Типи об'єднань також можуть бути розширені в параметрах і звужені в значеннях, що повертаються:
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.
Щоб залишити коментар, будь ласка, увійдіть до системи