PHP 8.0: Noutăți în tipurile de date (2/4)

acum 4 ani De la David Grudl  

Versiunea 8.0 a PHP tocmai a fost lansată. Este plină de caracteristici noi, ca nicio versiune anterioară. Introducerea lor merită patru articole separate. În cea de-a doua parte, vom arunca o privire asupra tipurilor de date.

Să ne întoarcem în istorie. Introducerea indicilor de tip scalar a fost un progres semnificativ în PHP 7. Aproape că nu s-a întâmplat. Andreu Faulds, autorul soluției ingenioase, care era în întregime retrocompatibilă și opțională datorită declare(strict_types=1), a fost respinsă cu duritate de către comunitate. Din fericire, Anthony Ferrara i-a luat apărarea ei și a propunerii la vremea respectivă, a lansat o campanie și RFC-ul a trecut foarte aproape. Uf. Cele mai multe dintre noutățile din PHP 8 sunt grație legendarului Nikita Popov și toate au trecut de vot ca un cuțit prin unt. Lumea se schimbă în bine.

PHP 8 aduce tipurile la perfecțiune. Marea majoritate a adnotărilor phpDoc, cum ar fi @param, @return și @var vor fi înlocuite cu notații native. Dar, cel mai important, tipurile vor fi verificate de motorul PHP. Doar descrierile structurilor precum string[] sau adnotările mai complexe pentru PHPStan vor rămâne în comentarii.

Tipuri de uniune

Tipurile de uniune sunt o enumerare a două sau mai multe tipuri pe care o variabilă le poate accepta:

class Button
{
	private string|object $caption;

	public function setCaption(string|object $caption)
	{
		$this->caption = $caption;
	}
}

Anumite tipuri de uniune au mai fost introduse în PHP. De exemplu, tipurile nulificabile. Cum ar fi ?string, care este echivalent cu tipul de uniune string|null. Notația cu semnul întrebării poate fi considerată o abreviere. Desigur, funcționează și în PHP 8, dar nu o puteți combina cu barele verticale. Astfel, în loc de ?string|object trebuie să scrieți string|object|null. În plus, iterable a fost întotdeauna echivalent cu array|Traversable. S-ar putea să fiți surprins de faptul că float este, de asemenea, un tip de uniune, deoarece acceptă atât int|float, dar transformă valoarea în float.

Nu puteți utiliza pseudotipurile void și mixed în uniuni, deoarece nu ar avea sens.

Nette este pregătit pentru tipurile de uniune. În Schema, Expect::from() le acceptă, iar prezentatorii le acceptă și ei. Le puteți utiliza în metodele de redare și de acțiune, de exemplu:

public function renderDetail(int|array $id)
{
	...
}

Pe de altă parte, autowiring în Nette DI respinge tipurile de uniune. Până în prezent, nu există niciun caz de utilizare în care ar avea sens ca constructorul să accepte fie unul, fie un alt obiect. Desigur, dacă apare un astfel de caz de utilizare, va fi posibil să se ajusteze comportamentul containerului în consecință.

Metodele getParameterType(), getReturnType() și getPropertyType() din Nette\Utils\Reflection aruncă o excepție în cazul tipului de uniune (asta în versiunea 3.1; în versiunea anterioară 3.0, aceste metode se întorc pentru a menține compatibilitatea cu null).

mixed

Pseudotipul mixed acceptă orice valoare.

În cazul parametrilor și al proprietăților, acesta are același comportament ca și în cazul în care nu se specifică niciun tip. Așadar, care este scopul său? Pentru a distinge când un tip lipsește pur și simplu și când este intenționat mixed.

În cazul valorilor de retur ale funcțiilor și metodelor, faptul că nu se specifică tipul diferă de utilizarea mixed. Înseamnă opusul lui void, deoarece necesită ca funcția să returneze ceva. O returnare lipsă produce atunci o eroare fatală.

În practică, ar trebui să o utilizați rar, deoarece, datorită tipurilor de uniune, puteți specifica valoarea cu precizie. Prin urmare, este adecvată doar în situații unice:

function dump(mixed $var): mixed
{
	// print variable
	return $var;
}

false

Spre deosebire de mixed, puteți utiliza noul pseudotip false exclusiv în tipurile de uniune. Acesta a apărut din necesitatea de a descrie tipul de retur al funcțiilor native, care în mod istoric returnează false în caz de eșec:

function strpos(string $haystack, string $needle): int|false
{
}

Prin urmare, nu există un tip true. Nu se poate folosi nici false singur, nici false|null, nici bool|false.

static

Pseudotipul static poate fi utilizat numai ca tip de retur al unei metode. Acesta spune că metoda returnează un obiect de același tip ca și obiectul însuși (în timp ce self spune că returnează clasa în care este definită metoda). Este excelent pentru a descrie interfețe fluente:

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

Acest tip nu există în PHP 8 și nu va fi introdus în viitor. Resursele sunt o relicvă din perioada în care PHP nu avea obiecte. Treptat, resursele vor fi înlocuite de obiecte. În cele din urmă, acestea vor dispărea complet. De exemplu, PHP 8.0 înlocuiește resursa imagine cu obiectul GdImage, iar resursa curl cu obiectul CurlHandle.

Stringable

Este o interfață care este implementată automat de fiecare obiect cu o metodă magică __toString().

class Email
{
	public function __toString(): string
	{
		return $this->value;
	}
}

function print(Stringable|string $s)
{
}

print('abc');
print(new Email);

Este posibil să se precizeze în mod explicit class Email implements Stringable în definiția clasei, dar nu este necesar.

Nette\Utils\Html reflectă, de asemenea, această schemă de denumire prin implementarea interfeței Nette\HtmlStringable în locul fostei IHtmlString. Obiectele de acest tip, de exemplu, nu sunt scăpate de Latte.

Tip varianță, contravarianță, covarianță

Principiul de substituție Liskov (Liskov Substitution Principle – LSP) prevede că clasele de extensie și implementările interfețelor nu trebuie niciodată să ceară mai mult și să ofere mai puțin decât părintele. Altfel spus, metoda copil nu trebuie să solicite mai multe argumente sau să accepte o gamă mai restrânsă de tipuri de parametri decât cea părinte și, invers, nu trebuie să returneze o gamă mai largă de tipuri. Dar poate returna mai puține. De ce? Pentru că, în caz contrar, moștenirea s-ar întrerupe. O funcție ar accepta un obiect de un anumit tip, dar nu ar avea nicio idee despre parametrii pe care îi poate transmite metodelor sale și despre tipurile pe care le-ar returna. Orice copil ar putea să o rupă.

Așadar, în POO, clasa copil poate:

  • accepta o gamă mai largă de tipuri în parametri (acest lucru se numește contravarianță)
  • să returneze o gamă mai restrânsă de tipuri (covarianță)
  • iar proprietățile nu își pot schimba tipul (sunt invariante).

PHP poate face acest lucru încă din versiunea 7.4, iar toate tipurile nou introduse în PHP 8.0 acceptă, de asemenea, contravarianța și covarianța.

În cazul lui mixed, copilul poate restrânge valoarea de returnare la orice tip, dar nu și void, deoarece acesta nu reprezintă o valoare, ci mai degrabă absența ei. De asemenea, copilul nu poate omite o declarație de tip, deoarece aceasta permite, de asemenea, absența unei valori.

class A
{
    public function foo(mixed $foo): mixed
    {}
}

class B extends A
{
    public function foo($foo): string
    {}
}

Tipurile de uniune pot fi, de asemenea, extinse în ceea ce privește parametrii și restrânse în ceea ce privește valorile de returnare:

class A
{
    public function foo(string|int $foo): string|int
    {}
}

class B extends A
{
    public function foo(string|int|float $foo): string
    {}
}

În plus, false poate fi extins la bool în parametru sau invers, bool la false în valoarea de returnare.

Toate infracțiunile împotriva covarianței/contravarianței conduc la o eroare fatală în PHP 8.0.

În următoarele părți ale acestei serii, vom arăta care sunt atributele, ce funcții și clase noi au apărut în PHP și vom prezenta Just in Time Compiler.