PHP 8.0: новости в типовете данни (2/4)
Току-що беше пусната версия 8.0 на PHP. Тя е пълна с нови функции, както никоя друга версия досега. Тяхното представяне заслужава четири отделни статии. Във втората част ще разгледаме типовете данни.
Нека се върнем назад в историята.
Въвеждането на подсказки за скаларни
типове беше значителен пробив в 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 е готов за съюзни типове. В Схемата 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.
Тип дисперсия, контравариантност, ковариантност
Принципът на заместване на Лисков (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.
За да изпратите коментар, моля, влезте в системата