PHP 8.0: Tipuri de date (2/4)
A fost lansată versiunea PHP 8.0. Este atât de plină de noutăți cum nu a mai fost nicio versiune înainte. Prezentarea lor a necesitat patru articole separate. În acest al doilea articol, vom analiza tipurile de date.

Să ne întoarcem în istorie. O descoperire fundamentală a PHP 7 a fost
introducerea type hint-urilor scalare. Aproape că nu s-a întâmplat. Autoarea
soluției uimitoare Andreu Faulds, care
datorită declare(strict_types=1)
era complet compatibilă invers
și opțională, a fost respinsă urât de comunitate. Din fericire, atât ea,
cât și propunerea ei au fost susținute atunci de Anthony Ferrara, care a lansat
o campanie și RFC-ul a trecut la limită. Ufff. Majoritatea noutăților din
PHP 8 sunt opera legendarului Nikita
Popov și în votare i-au trecut ca unse. Lumea se schimbă în bine.
PHP 8 duce tipurile la perfecțiune. Vor dispărea marea majoritate a
adnotărilor phpDoc @param
, @return
și
@var
din cod și vor fi înlocuite de notația nativă și, mai
ales, de verificarea de către motorul PHP. În comentarii vor rămâne doar
descrierile structurilor precum string[]
sau adnotări mai complexe
pentru PHPStan.
Tipuri Union
Tipurile Union sunt o enumerare a două sau mai multe tipuri pe care le poate lua o valoare:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
PHP cunoștea deja unele tipuri union speciale. De exemplu, tipurile nullable
precum ?string
, care este echivalentul tipului union
string|null
, iar notația cu semn de întrebare poate fi
considerată doar o prescurtare. Desigur, funcționează și în PHP 8, dar nu
poate fi combinată cu bare verticale, deci în loc de
?string|object
trebuie scris complet
string|object|null
. În plus, iterable
a fost
întotdeauna echivalentul array|Traversable
. Poate vă surprinde
faptul că și float
este de fapt un tip union care acceptă
int|float
, însă convertește la float
.
În union-uri nu se pot utiliza pseudotipurile void
și
mixed
, deoarece acest lucru nu ar avea niciun 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)
{
...
}
În schimb, autowiring-ul în Nette DI refuză tipurile union. Lipsește deocamdată un caz de utilizare în care ar avea sens ca, de exemplu, un constructor să accepte fie un obiect, fie altul. Desigur, dacă apare, va fi posibilă adaptarea comportamentului containerului în consecință.
Metodele getParameterType()
, getReturnType()
și
getPropertyType()
din Nette\Utils\Reflection aruncă o excepție
în cazul unui tip union (în versiunea 3.1, în versiunea mai veche
3.0 returnează null din motive de compatibilitate).
mixed
Pseudotipul mixed
indică faptul că valoarea poate fi
absolut orice.
În cazul parametrilor și proprietăților, este de fapt același
comportament ca atunci când nu specificăm niciun tip. La ce este bun atunci?
Pentru a putea distinge când tipul pur și simplu lipsește și când este
într-adevăr mixed
.
În cazul valorii returnate de funcție și metodă, nespecificarea tipului
diferă de specificarea tipului mixed
. Este de fapt opusul lui
void
, deoarece indică faptul că funcția trebuie să returneze
ceva. Lipsa return-ului este atunci o eroare fatală.
În practică, ar trebui să-l utilizați rar, deoarece datorită tipurilor union puteți specifica valoarea mai precis. Se potrivește deci în situații excepționale:
function dump(mixed $var): mixed
{
// afișează variabila
return $var;
}
false
Noul pseudotip false
poate fi utilizat, în schimb, doar în
tipurile union. A apărut din nevoia de a descrie nativ tipul valorii returnate
pentru funcțiile native, care istoric returnează false în caz de eșec:
function strpos(string $haystack, string $needle): int|false
{
}
Din acest motiv, nu există tipul true
, nu se poate utiliza nici
false
singur sau false|null
sau
bool|false
.
static
Pseudotipul static
poate fi utilizat doar ca tip de return al
unei metode. Indică faptul că metoda returnează un obiect de același tip ca
obiectul însuși (în timp ce self
indică faptul că returnează
clasa în care este definită metoda). Ceea ce se potrivește excelent pentru
descrierea interfețelor 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 nici nu va fi introdus în viitor.
Resursele sunt o relicvă istorică din vremurile în care PHP nu avea încă
obiecte. Treptat, resursele vor fi înlocuite cu obiecte și, în timp, acest
tip va dispărea complet. De exemplu, PHP 8.0 înlocuiește resursa care
reprezintă o imagine cu obiectul GdImage
și resursa conexiunii
curl
cu obiectul CurlHandle
.
Stringable
Este o interfață pe care o implementează automat fiecare obiect cu
metoda magică __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
În definiția clasei, este posibil să se specifice explicit
class Email implements Stringable
, dar nu este necesar.
Acest stil de denumire este reflectat și de Nette\Utils\Html
,
care implementează interfața Nette\HtmlStringable
în locul celei
anterioare IHtmlString
. Obiectele de acest tip, de exemplu, nu sunt
escapate de Latte.
Varianța tipului, contravarianța, covarianța
Principiul de substituție Liskov (Liskov Substitution Principle – LSP) spune că descendenții unei clase și implementările unei interfețe nu trebuie să necesite niciodată mai mult și să furnizeze mai puțin decât părintele. Adică, metoda unui descendent nu trebuie să necesite mai multe argumente sau să accepte la parametri un interval mai restrâns de tipuri decât strămoșul și, invers, nu trebuie să returneze un interval mai larg de tipuri. Dar poate returna un interval mai restrâns. De ce? Deoarece altfel moștenirea nu ar funcționa deloc. Funcția ar accepta un obiect de un anumit tip, dar nu ar ști ce parametri pot fi transmiși metodelor și ce vor returna efectiv, deoarece orice descendent ar putea încălca acest lucru.
Deci, în OOP este valabil faptul că un descendent poate:
- în parametri să accepte un interval mai larg de tipuri (acest lucru se numește contravarianță)
- să returneze un interval mai restrâns de tipuri (covarianță)
- și proprietățile nu pot schimba tipul (sunt invariante)
PHP știe acest lucru de la versiunea 7.4 și toate tipurile nou introduse în PHP 8.0 suportă, de asemenea, contravarianța și covarianța.
În cazul mixed
, descendentul poate restrânge valoarea
returnată la orice tip, însă nu void
, deoarece nu este vorba de
un tip de valoare, ci de absența acesteia. Nici descendentul nu poate să nu
specifice tipul, deoarece și acest lucru permite absența.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
De asemenea, tipurile union pot fi extinse în parametri și restrânse în valorile returnate:
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 în parametru la
bool
sau, invers, bool
în valoarea returnată poate
fi restrâns la false
.
Toate încălcările împotriva covarianței/contravarianței duc în PHP 8.0 la o eroare fatală.
În următoarele părți vom arăta ce sunt atributele, ce funcții și clase noi au apărut în PHP și vom prezenta Just in Time Compiler.
Pentru a trimite un comentariu, vă rugăm să vă conectați