PHP 8.0: Nowości w typach danych (2/4)
PHP w wersji 8.0 zostało właśnie wydane. Jest ona pełna nowych funkcji, jak żadna wcześniejsza wersja. Ich wprowadzenie zasługuje na cztery osobne artykuły. W drugiej części przyjrzymy się typom danych.
Cofnijmy się do historii. Wprowadzenie podpowiedzi typów skalarnych było
znaczącym przełomem w PHP 7. O mały włos nie doszło do niego. Andreu Faulds, autor genialnego rozwiązania,
które było całkowicie kompatybilne wstecz i opcjonalne dzięki
declare(strict_types=1)
, został ostro odrzucony przez
społeczność. Na szczęście Anthony
Ferrara bronił jej i propozycji w tym czasie, rozpoczął kampanię i RFC
przeszedł bardzo blisko. Whew. Większość nowości w PHP 8 jest dzięki
uprzejmości legendarnego Nikity Popova
i wszystkie przeszły przez głosowanie jak nóż przez masło. Świat zmienia
się na lepsze.
PHP 8 doprowadza typy do perfekcji. Zdecydowana większość adnotacji
phpDoc jak @param
, @return
i @var
zostanie zastąpiona natywną notacją. Ale co najważniejsze, typy będą
sprawdzane przez silnik PHP. W komentarzach pozostaną jedynie opisy struktur
takich jak string[]
czy bardziej złożone adnotacje dla
PHPStan.
Typy związków
Typy unijne to wyliczenie dwóch lub więcej typów, które może zaakceptować zmienna:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
Niektóre typy unii zostały wprowadzone do PHP już wcześniej. Na przykład
typy nullable. Takie jak ?string
, który jest równoważny typowi
unii string|null
. Notacja znaku zapytania może być uważana za
skrót. Oczywiście działa on również w PHP 8, ale nie można go połączyć
z pionowymi paskami. Tak więc zamiast ?string|object
musisz
napisać string|object|null
. Ponadto, iterable
zawsze
był równoważny array|Traversable
. Możesz być zaskoczony, że
float
jest również typem związkowym, ponieważ akceptuje
zarówno int|float
, ale rzutuje wartość na
float
.
Nie można używać pseudotypów void
i mixed
w
związkach, ponieważ nie miałoby to sensu.
Nette jest gotowe na typy związkowe. W Schema, Expect::from()
akceptuje je, a prezentery również je akceptują. Możesz ich używać na
przykład w metodach renderowania i działania:
public function renderDetail(int|array $id)
{
...
}
Z drugiej strony, autowiring w Nette DI odrzuca typy unii. Jak dotąd nie ma przypadku użycia, w którym miałoby sens, aby konstruktor akceptował taki lub inny obiekt. Oczywiście, jeśli taki use-case się pojawi, będzie można odpowiednio dostosować zachowanie kontenera.
Metody getParameterType()
, getReturnType()
i
getPropertyType()
w Nette\Utils\Reflection
rzucają
wyjątek w przypadku typu union (tak jest w wersji 3.1; we wcześniejszej wersji
3.0 metody te zwracają, aby zachować kompatybilność null).
mixed
Pseudotyp mixed
akceptuje dowolną wartość.
W przypadku parametrów i właściwości powoduje takie samo zachowanie, jak
gdybyśmy nie określili żadnego typu. Jaki jest więc jego cel? Aby
rozróżnić, kiedy typ jest jedynie pominięty, a kiedy jest celowo
mixed
.
W przypadku wartości zwracanych przez funkcje i metody, nieokreślenie typu
różni się od użycia mixed
. Oznacza to przeciwieństwo
void
, ponieważ wymaga, aby funkcja zwróciła coś. Brakujący
return wywołuje wtedy błąd fatalny.
W praktyce powinieneś rzadko go używać, ponieważ dzięki typom unii możesz dokładnie określić wartość. Dlatego nadaje się tylko w wyjątkowych sytuacjach:
function dump(mixed $var): mixed
{
// print variable
return $var;
}
false
W przeciwieństwie do mixed
, nowego pseudotypu
false
można używać wyłącznie w typach unii. Powstał on
z potrzeby opisania typu zwracanego przez funkcje natywne, które historycznie
zwracają false w przypadku niepowodzenia:
function strpos(string $haystack, string $needle): int|false
{
}
Dlatego nie ma typu true
. Nie można też używać samego
false
, ani false|null
, ani
bool|false
.
static
Pseudotyp static
może być użyty tylko jako typ zwrotny
metody. Mówi, że metoda zwraca obiekt tego samego typu co sam obiekt (podczas
gdy self
mówi, że zwraca klasę, w której zdefiniowana jest
metoda). Jest to doskonałe rozwiązanie do opisywania płynnych
interfejsów:
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
Ten typ nie istnieje w PHP 8 i nie będzie wprowadzony w przyszłości.
Zasoby są reliktem z czasów, gdy PHP nie posiadał obiektów. Stopniowo
zasoby będą zastępowane przez obiekty. W końcu znikną całkowicie. Na
przykład PHP 8.0 zastępuje zasób image obiektem GdImage
, a
zasób curl obiektem CurlHandle
.
Stringable
Jest to interfejs, który zostaje automatycznie zaimplementowany przez każdy
obiekt z magiczną metodą __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
Możliwe jest wyraźne stwierdzenie
class Email implements Stringable
w definicji klasy, ale nie jest
to konieczne.
Nette\Utils\Html
również odzwierciedla ten schemat nazewnictwa
poprzez implementację interfejsu Nette\HtmlStringable
zamiast
dawnego IHtmlString
. Obiekty tego typu, na przykład, nie są
wymykane przez Latte.
Wariancja typu, kontrawariancja, kowariancja
Zasada substytucji Liskov (LSP) mówi, że klasy rozszerzające i implementacje interfejsów nigdy nie mogą wymagać więcej i dostarczać mniej niż rodzic. To znaczy, metoda dziecka nie może wymagać więcej argumentów lub akceptować węższego zakresu typów dla parametrów niż rodzic i odwrotnie, nie może zwrócić szerszego zakresu typów. Ale może zwrócić mniejszą liczbę. Dlaczego. Ponieważ w przeciwnym razie dziedziczenie zostałoby złamane. Funkcja akceptowałaby obiekt określonego typu, ale nie miałaby pojęcia, jakie parametry może przekazać do swoich metod i jakie typy zwrócą. Każde dziecko mogłoby ją złamać.
Więc w OOP, klasa dziecka może:
- przyjmować szerszy zakres typów w parametrach (nazywa się to kontrawariantnością)
- zwracać węższy zakres typów (kowariancja)
- a właściwości nie mogą zmieniać typu (są niezmienne)
PHP potrafi to robić od wersji 7.4, a wszystkie nowo wprowadzone typy w PHP 8.0 również obsługują contravariance i covariance.
W przypadku mixed
, dziecko może zawęzić wartość zwracaną
do dowolnego typu, ale nie void
, ponieważ nie reprezentuje ona
wartości, ale raczej jej brak. Dziecko nie może również pominąć deklaracji
typu, ponieważ pozwala to również na brak wartości.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Typy Unii mogą być również rozszerzane w parametrach i zawężane w wartościach zwracanych:
class A
{
public function foo(string|int $foo): string|int
{}
}
class B extends A
{
public function foo(string|int|float $foo): string
{}
}
Ponadto false
można rozszerzyć do bool
w
parametrze lub odwrotnie bool
do false
w wartości
zwrotnej.
Wszystkie wykroczenia przeciwko kowariancji/kontrawariancji prowadzą do błędu krytycznego w PHP 8.0.
*W kolejnych częściach tej serii pokażemy, czym są atrybuty, jakie nowe funkcje i klasy pojawiły się w PHP oraz przedstawimy Just in Time Compiler.
Aby przesłać komentarz, proszę się zalogować