PHP 8.0: novità nei tipi di dati (2/4)
È stata appena rilasciata la versione 8.0 di PHP. È ricca di nuove funzionalità, come nessuna versione precedente. La loro introduzione merita quattro articoli separati. Nella seconda parte, daremo uno sguardo ai tipi di dati.
Facciamo un passo indietro nella storia. L'introduzione dei suggerimenti per
i tipi scalari ha rappresentato una svolta significativa in PHP 7. Per poco
non è stata realizzata. Andreu Faulds,
l'autore dell'ingegnosa soluzione, interamente retrocompatibile e opzionale
grazie a declare(strict_types=1)
, fu duramente respinto dalla
comunità. Fortunatamente, Anthony
Ferrara difese lei e la proposta all'epoca, lanciò una campagna e l'RFC
passò a stretto giro. Whew. La maggior parte delle novità di PHP 8 sono per
gentile concessione del leggendario Nikita
Popov e tutte hanno superato la votazione come un coltello nel burro. Il
mondo sta cambiando in meglio.
PHP 8 porta i tipi alla perfezione. La maggior parte delle annotazioni di
phpDoc, come @param
, @return
e @var
,
saranno sostituite da notazioni native. Ma soprattutto, i tipi saranno
controllati dal motore di PHP. Solo le descrizioni di strutture come
string[]
o le annotazioni più complesse per PHPStan rimarranno
nei commenti.
Tipi di unione
I tipi union sono un'enumerazione di due o più tipi che una variabile può accettare:
class Button
{
private string|object $caption;
public function setCaption(string|object $caption)
{
$this->caption = $caption;
}
}
Alcuni tipi di unione sono già stati introdotti in PHP. I tipi nulli, per
esempio. Come ?string
, che è equivalente al tipo union
string|null
. La notazione a punto interrogativo può essere
considerata un'abbreviazione. Naturalmente, funziona anche in PHP 8, ma non si
può combinare con le barre verticali. Quindi, invece di
?string|object
si deve scrivere string|object|null
.
Inoltre, iterable
è sempre stato equivalente a
array|Traversable
. Si potrebbe essere sorpresi dal fatto che anche
float
è un tipo union, in quanto accetta sia
int|float
, sia float
.
Non è possibile utilizzare gli pseudotipi void
e
mixed
nelle unioni, perché non avrebbe senso.
Nette è pronto per i tipi unione. In Schema, Expect::from()
li accetta e anche i presentatori li accettano. Si possono usare, ad esempio,
nei metodi di rendering e di azione:
public function renderDetail(int|array $id)
{
...
}
D'altra parte, il cablaggio automatico in Nette DI rifiuta i tipi di unione. Finora non esiste un caso d'uso in cui abbia senso che il costruttore accetti uno o l'altro oggetto. Naturalmente, se tale caso d'uso dovesse presentarsi, sarà possibile adattare il comportamento del contenitore di conseguenza.
I metodi getParameterType()
, getReturnType()
e
getPropertyType()
in Nette\Utils\Reflection
lanciano
un'eccezione nel caso del tipo union (questo nella versione 3.1; nella
precedente versione 3.0, questi metodi ritornano per mantenere la compatibilità
con null).
mixed
Lo pseudotipo mixed
accetta qualsiasi valore.
Nel caso di parametri e proprietà, si ottiene lo stesso comportamento che si
avrebbe se non si specificasse alcun tipo. Qual è il suo scopo? Distinguere
quando un tipo è semplicemente mancante e quando è intenzionalmente
mixed
.
Nel caso dei valori di ritorno di funzioni e metodi, non specificare il tipo
è diverso dall'utilizzare mixed
. Significa l'opposto di
void
, poiché richiede che la funzione restituisca qualcosa. Un
ritorno mancante produce quindi un errore fatale.
In pratica, si dovrebbe usare raramente, perché grazie ai tipi union è possibile specificare con precisione il valore. È quindi adatto solo in situazioni particolari:
function dump(mixed $var): mixed
{
// print variable
return $var;
}
false
A differenza di mixed
, il nuovo pseudotipo false
può essere utilizzato esclusivamente nei tipi union. È nato dall'esigenza di
descrivere il tipo di ritorno delle funzioni native, che storicamente
restituiscono false in caso di fallimento:
function strpos(string $haystack, string $needle): int|false
{
}
Pertanto, non esiste il tipo true
. Non è possibile utilizzare
solo false
, né false|null
, né
bool|false
.
static
Lo pseudotipo static
può essere usato solo come tipo di ritorno
di un metodo. Dice che il metodo restituisce un oggetto dello stesso tipo
dell'oggetto stesso (mentre self
dice che restituisce la classe in
cui il metodo è definito). È eccellente per descrivere interfacce fluenti:
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 in futuro. Le risorse
sono una reliquia del periodo in cui PHP non aveva oggetti. Gradualmente, le
risorse saranno sostituite dagli oggetti. Alla fine, scompariranno
completamente. Ad esempio, PHP 8.0 sostituisce la risorsa immagine con
l'oggetto GdImage
e la risorsa curl con l'oggetto
CurlHandle
.
Stringable
È un'interfaccia che viene implementata automaticamente da ogni oggetto con
un metodo magico __toString()
.
class Email
{
public function __toString(): string
{
return $this->value;
}
}
function print(Stringable|string $s)
{
}
print('abc');
print(new Email);
È possibile dichiarare esplicitamente
class Email implements Stringable
nella definizione della classe,
ma non è necessario.
Nette\Utils\Html
riflette anche questo schema di denominazione,
implementando l'interfaccia Nette\HtmlStringable
invece della
precedente IHtmlString
. Gli oggetti di questo tipo, ad esempio, non
vengono evasi da Latte.
Tipo varianza, controvarianza, covarianza
Il principio di sostituzione di Liskov (LSP) stabilisce che le classi di estensione e le implementazioni di interfacce non devono mai richiedere di più e fornire di meno rispetto al genitore. In altre parole, il metodo figlio non deve richiedere più argomenti o accettare una gamma più ristretta di tipi di parametri rispetto al genitore e, viceversa, non deve restituire una gamma più ampia di tipi. Ma può restituirne di meno. Perché? Perché altrimenti l'ereditarietà si interromperebbe. Una funzione accetterebbe un oggetto di un tipo specifico, ma non avrebbe idea di quali parametri può passare ai suoi metodi e quali tipi restituirebbe. Qualsiasi figlio potrebbe romperla.
Quindi, nell'OOP, la classe figlio può:
- accettare una gamma più ampia di tipi nei parametri (questa è chiamata contravarianza)
- restituire una gamma più ristretta di tipi (covarianza)
- e le proprietà non possono cambiare tipo (sono invarianti).
PHP è in grado di farlo dalla versione 7.4 e tutti i nuovi tipi introdotti in PHP 8.0 supportano anche la contravarianza e la covarianza.
Nel caso di mixed
, il figlio può restringere il valore di
ritorno a qualsiasi tipo, ma non void
, poiché non rappresenta un
valore, ma piuttosto la sua assenza. Inoltre, il bambino non può omettere la
dichiarazione del tipo, poiché anche in questo caso si può parlare di assenza
di valore.
class A
{
public function foo(mixed $foo): mixed
{}
}
class B extends A
{
public function foo($foo): string
{}
}
I tipi di unione possono anche essere estesi 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 esteso a bool
nel
parametro o viceversa bool
a false
nel valore di
ritorno.
Tutte le infrazioni alla covarianza/contravarianza comportano un errore fatale in PHP 8.0.
Nelle prossime parti di questa serie, mostreremo cosa sono gli attributi, quali nuove funzioni e classi sono apparse in PHP e introdurremo il compilatore Just in Time.
Per inviare un commento, effettuare il login