PHP 8.0: Tipi di dati (2/4)
È stata rilasciata la versione 8.0 di PHP. È così ricca di novità come nessuna versione precedente. La loro presentazione ha richiesto ben quattro articoli separati. In questo secondo, esamineremo i tipi di dati.

Torniamo alla storia. La svolta fondamentale di PHP 7 è stata
l'introduzione dei type hint scalari. Per poco non è successo. L'autrice della
straordinaria soluzione Andrea Faulds, che
grazie a declare(strict_types=1)
era completamente retrocompatibile
e opzionale, è stata respinta bruscamente dalla comunità. Fortunatamente, Anthony Ferrara ha preso le sue difese e
quelle della sua proposta, ha lanciato una campagna e l'RFC è passata per un
pelo. Ufff. La maggior parte delle novità in PHP 8 è opera del leggendario Nikita Popov e nelle votazioni gli sono
passate lisce come l'olio. Il mondo sta cambiando in meglio.
PHP 8 porta i tipi alla perfezione. Scomparirà la stragrande maggioranza
delle annotazioni phpDoc @param
, @return
e
@var
nel codice e saranno sostituite dalla notazione nativa e
soprattutto dal controllo da parte del motore PHP. Nei commenti rimarranno solo
le descrizioni di strutture come string[]
o annotazioni più
complesse per PHPStan.
Union Types
I tipi unione sono un elenco di due o più tipi che un valore può assumere:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
PHP conosceva già alcuni tipi unione speciali in precedenza. Ad esempio,
i tipi nullable come ?string
, che è l'equivalente del tipo unione
string|null
e la notazione con il punto interrogativo può essere
considerata solo una scorciatoia. Ovviamente funziona anche in PHP 8, ma non
può essere combinato con le barre verticali, quindi invece di
?string|object
è necessario scrivere il completo
string|object|null
. Inoltre, iterable
è sempre stato
l'equivalente di array|Traversable
. Potrebbe sorprendervi che anche
float
sia in realtà un tipo unione che accetta
int|float
, ma effettua il cast a float
.
Negli unioni non è possibile utilizzare gli pseudo-tipi void
e
mixed
, perché non avrebbe alcun senso.
Nette è pronto per i tipi unione. In Schema, Expect::from()
li
comprende: Expect::from(),
li comprendono anche i presenter, quindi puoi usarli ad esempio nei metodi
render e action:
public function renderDetail(int|array $id)
{
...
}
Al contrario, l'autowiring in Nette DI rifiuta i tipi unione. Manca ancora un caso d'uso in cui avrebbe senso che, ad esempio, un costruttore accettasse o questo o quell'oggetto. Ovviamente, se dovesse emergere, sarà possibile modificare di conseguenza il comportamento del container.
I metodi getParameterType()
, getReturnType()
e
getPropertyType()
in Nette\Utils\Reflection lanciano un'eccezione
in caso di tipo unione (nella versione 3.1, nella precedente 3.0 restituiscono
null per compatibilità).
mixed
Lo pseudo-tipo mixed
indica che il valore può essere
assolutamente qualsiasi cosa.
Nel caso di parametri e proprietà, si tratta in realtà dello stesso
comportamento di quando non specifichiamo alcun tipo. A cosa serve allora? Per
poter distinguere quando il tipo semplicemente manca e quando è veramente
mixed
.
Nel caso del valore di ritorno di una funzione o di un metodo, non
specificare il tipo differisce dallo specificare il tipo mixed
. Si
tratta in realtà dell'opposto di void
, poiché indica che la
funzione deve restituire qualcosa. Un return mancante è quindi un errore
fatale.
In pratica, dovresti usarlo raramente, perché grazie ai tipi unione puoi specificare il valore in modo più preciso. È quindi utile in situazioni eccezionali:
function dump(mixed $var): mixed
{
// stampa la variabile
return $var;
}
false
Il nuovo pseudo-tipo false
può invece essere utilizzato solo
nei tipi unione. È nato dalla necessità di descrivere nativamente il tipo del
valore di ritorno delle funzioni native, che storicamente restituiscono false in
caso di fallimento:
function strpos(string $haystack, string $needle): int|false
{
}
Per questo motivo, non esiste il tipo true
, non è possibile
utilizzare nemmeno false
da solo o false|null
o
bool|false
.
static
Lo pseudo-tipo static
può essere utilizzato solo come tipo di
ritorno di un metodo. Indica che il metodo restituisce un oggetto dello stesso
tipo dell'oggetto stesso (mentre self
indica che restituisce la
classe in cui è definito il metodo). Il che è ottimo per descrivere le
interfacce fluent:
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
Questo tipo non esiste in PHP 8 e non sarà introdotto nemmeno in futuro. Le
risorse sono una reliquia storica dei tempi in cui PHP non aveva ancora oggetti.
Gradualmente, le risorse verranno sostituite da oggetti e col tempo questo tipo
scomparirà completamente. Ad esempio, PHP 8.0 sostituisce la risorsa che
rappresenta un'immagine con l'oggetto GdImage
e la risorsa di
connessione curl
con l'oggetto CurlHandle
.
Stringable
Si tratta di un'interfaccia che viene implementata automaticamente da ogni
oggetto con il metodo magico __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
Nella definizione della classe è possibile specificare esplicitamente
class Email implements Stringable
, ma non è necessario.
Questo stile di denominazione si riflette anche in
Nette\Utils\Html
, che implementa l'interfaccia
Nette\HtmlStringable
invece della precedente
IHtmlString
. Oggetti di questo tipo, ad esempio, non vengono
escapati da Latte.
Varianza dei tipi, controvarianza, covarianza
Il principio di sostituibilità di Liskov (Liskov Substitution Principle – LSP) afferma che i discendenti di una classe e le implementazioni di un'interfaccia non devono mai richiedere di più e fornire di meno del genitore. Cioè, che il metodo di un discendente non deve richiedere più argomenti o accettare per i parametri un intervallo di tipi più ristretto rispetto all'antenato e, al contrario, non deve restituire un intervallo di tipi più ampio. Ma può restituire un intervallo più ristretto. Perché? Perché altrimenti l'ereditarietà non funzionerebbe affatto. Una funzione accetterebbe sì un oggetto di un certo tipo, ma non saprebbe quali parametri passare ai metodi e cosa restituiranno effettivamente, perché qualsiasi discendente potrebbe infrangerlo.
Quindi, in OOP vale che un discendente può:
- nei parametri accettare un intervallo di tipi più ampio (questo si chiama controvarianza)
- restituire un intervallo di tipi più ristretto (covarianza)
- e le proprietà non possono cambiare tipo (sono invarianti)
PHP è in grado di farlo dalla versione 7.4 e tutti i tipi appena introdotti in PHP 8.0 supportano anche la controvarianza e la covarianza.
Nel caso di mixed
, un discendente può restringere il valore di
ritorno a qualsiasi tipo, ma non void
, perché non si tratta di un
tipo di valore, ma della sua assenza. Nemmeno un discendente può non
specificare il tipo, perché anche questo ammette l'assenza.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
Anche i tipi unione possono essere ampliati nei parametri e ristretti nei valori di ritorno:
class A
{
public function foo(string|int $foo): string|int
{}
}
class B extends A
{
public function foo(string|int|float $foo): string
{}
}
Inoltre, false
può essere ampliato in un parametro a
bool
o, al contrario, bool
in un valore di ritorno
può essere ristretto a false
.
Tutte le violazioni della covarianza/controvarianza portano in PHP 8.0 a un fatal error.
Nelle prossime parti mostreremo cosa sono gli attributi, quali nuove funzioni e classi sono apparse in PHP e presenteremo il Just in Time Compiler.
Per inviare un commento, effettuare il login