Benvenuti nel mondo entusiasmante di PHP! Se sei un developer web alle prime armi, o anche con un po' di esperienza, saprai che la programmazione è un campo in continua evoluzione. Uno dei linguaggi più longevi e diffusi per lo sviluppo web, PHP, non fa eccezione. Con il rilascio di PHP 8, abbiamo assistito a un vero e proprio salto di qualità, introducendo funzionalità che non solo migliorano le performance, ma rendono anche il codice più pulito, leggibile e sicuro.
Questo articolo è la tua guida completa alle principali novità di PHP 8. Non ci limiteremo a elencare le nuove funzionalità, ma le esploreremo in profondità, spiegando il "perché" dietro ogni cambiamento e fornendo esempi pratici che potrai replicare subito. L'obiettivo è farti comprendere non solo cosa puoi fare con PHP 8, ma anche come queste innovazioni possano migliorare il tuo flusso di lavoro e la qualità del tuo software.
PHP 8 è una major release che ha portato con sé un'ondata di entusiasmo nella comunità. Ha introdotto miglioramenti significativi a livello di performance, nuove funzionalità sintattiche che semplificano il codice e un focus maggiore sulla robustezza e la tipizzazione. Queste novità non sono solo per i guru di PHP; sono strumenti preziosi per ogni sviluppatore, specialmente per chi sta muovendo i primi passi e vuole scrivere codice moderno ed efficiente fin da subito.
Preparati a scoprire come PHP 8 sta ridefinendo lo sviluppo web server-side!
1. Il JIT Compiler: Un Salto Quantico nelle Performance
Una delle novità più significative e attese di PHP 8 è l'introduzione del JIT (Just In Time) Compiler. Per capire la sua importanza, dobbiamo fare un piccolo passo indietro e capire come funziona PHP tradizionalmente.
Come Funziona PHP Senza JIT?
Tradizionalmente, quando esegui uno script PHP, il processo è il seguente:
- Parsing: Il codice PHP viene letto e convertito in un Abstract Syntax Tree (AST).
- Compilazione: L'AST viene poi compilato in bytecode (chiamato Opcode), che è una rappresentazione intermedia più vicina al linguaggio macchina ma ancora indipendente dalla CPU.
- Esecuzione: Il motore Zend di PHP esegue l'Opcode, interpretandolo riga per riga.
Questo processo è efficiente, ma l'interpretazione dell'Opcode può essere un collo di bottiglia per applicazioni molto complesse o con un'alta intensità computazionale. Ogni volta che lo script viene eseguito, l'Opcode viene interpretato da capo (anche se OPcache aiuta a memorizzare l'Opcode compilato per evitare il parsing e la compilazione ad ogni richiesta).
Cosa Cambia con il JIT?
Il JIT Compiler aggiunge un ulteriore strato a questo processo. Invece di limitarsi a interpretare l'Opcode, il JIT può identificare le parti del codice che vengono eseguite più frequentemente (le cosiddette "hot paths") e compilarle direttamente in codice macchina nativo durante l'esecuzione del programma. Questo codice macchina nativo può essere eseguito direttamente dalla CPU, bypassando la fase di interpretazione dell'Opcode.
Perché è così importante?
- Maggiore Velocità: Il codice macchina nativo è intrinsecamente più veloce dell'Opcode interpretato. Per carichi di lavoro CPU-bound (calcoli matematici complessi, manipolazione di immagini, algoritmi di machine learning), il JIT può portare a miglioramenti di performance significativi, a volte anche del 2-3x.
- Efficienza: Riduce il sovraccarico dell'interprete, rendendo l'esecuzione complessiva più efficiente.
È importante notare che il JIT non è una bacchetta magica che velocizza ogni applicazione PHP. Le applicazioni web tipiche, che passano molto tempo ad aspettare I/O (input/output) come le query al database o le chiamate API, potrebbero non vedere benefici enormi dal JIT. Tuttavia, per compiti intensivi di CPU, è un game-changer.
Il JIT è abilitato tramite la configurazione di OPcache. Puoi abilitarlo aggiungendo o modificando queste righe nel tuo php.ini:
[opcache]
opcache.enable=1
opcache.jit_buffer_size=100M ; Alloca memoria per il codice JIT compilato
opcache.jit=1255 ; Modalità di compilazione JIT (spiegato più avanti)
Il valore opcache.jit=1255 è un'impostazione comune che abilita diverse ottimizzazioni JIT. Per i principianti, basta sapere che questo "accende" il JIT. Per approfondire, i numeri rappresentano un bitmask per diverse strategie JIT (ad esempio, 1 abilita il tracing JIT, 2 abilita il function JIT, ecc.).
2. Named Arguments (Argomenti Nominati): Chiarezza e Flessibilità
Quante volte ti sei trovato a chiamare una funzione con molti parametri, magari tutti dello stesso tipo, e ti sei chiesto: "Qual è il significato del terzo parametro? E del quinto?". I Named Arguments risolvono esattamente questo problema.
Prima di PHP 8, dovevi passare gli argomenti in base alla loro posizione:
function createUser(string $name, string $email, ?string $phone = null, ?int $age = null, bool $isActive = true) {
// ... logica per creare l'utente
echo "Utente: $name, Email: $email\
";
if ($phone) echo "Telefono: $phone\
";
if ($age) echo "Età: $age\
";
echo "Attivo: " . ($isActive ? 'Sì' : 'No') . "\
";
}
// Chiamata tradizionale: difficile capire cosa sia 'null', 30, true
createUser('Mario Rossi', 'mario.rossi@example.com', null, 30, true);
Con i Named Arguments, puoi specificare il nome del parametro quando lo passi alla funzione. Questo ha diversi vantaggi:
- Leggibilità Migliorata: Il codice diventa molto più facile da capire, specialmente per funzioni con molti parametri.
- Ordine Indipendente: Non devi più preoccuparti dell'ordine dei parametri (a meno che tu non stia mescolando Named Arguments con positional ones, in quel caso i positional devono venire prima).
- Omettere Parametri Opzionali: Puoi omettere i parametri con valori di default senza dover passare
nullo i valori di default per i parametri precedenti.
Ecco come si presenta con i Named Arguments:
// Con Named Arguments: molto più chiaro!
createUser(name: 'Mario Rossi',
email: 'mario.rossi@example.com',
age: 30,
isActive: true);
// Puoi anche mescolare, ma i positional devono venire prima
createUser('Luigi Verdi', email: 'luigi.verdi@example.com', phone: '123-456-7890');
Questa funzionalità non è solo una comodità; è uno strumento potente per la manutenzione del codice e per la collaborazione in team. Rende le API delle tue funzioni più auto-documentanti e riduce la probabilità di errori dovuti a parametri mal posizionati.
3. Attributes (Attributi): Metadati nel Codice
Gli Attributes, precedentemente noti come Annotations, introducono un modo per aggiungere metadati strutturati direttamente al codice PHP. Se hai familiarità con linguaggi come Java (con le sue @Annotation) o C# (con i suoi [Attribute]), il concetto ti sarà familiare. In PHP, gli Attributes sono definiti usando #[...].
A Cosa Servono gli Attributes?
Gli Attributes ti permettono di allegare informazioni aggiuntive a classi, metodi, proprietà, funzioni e parametri. Queste informazioni possono poi essere lette in fase di runtime tramite la Reflection API di PHP. Gli usi tipici includono:
- Routing: Definire percorsi URL direttamente sui metodi dei controller.
- Validazione: Specificare regole di validazione per le proprietà di un oggetto.
- Autorizzazione: Indicare i permessi richiesti per accedere a un metodo.
- ORM (Object-Relational Mapping): Mappare le proprietà delle classi alle colonne del database.
- Generazione di Documentazione: Arricchire la documentazione automatica.
Come si Usano gli Attributes?
Prima di tutto, devi definire la classe dell'Attribute. Questa classe deve essere prefissata con l'Attribute #[Attribute] per indicare che è un Attribute.
// Definizione di una classe Attribute per la validazione
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::TARGET_PARAMETER)]
class MinLength
{
public function __construct(public int $length) {}
}
#[Attribute(Attribute::TARGET_METHOD)]
class Route
{
public function __construct(public string $path, public array $methods = ['GET']) {}
}
// Utilizzo degli Attributes su una classe e le sue proprietà/metodi
class UserRequest
{
#[MinLength(length: 5)]
public string $username;
#[MinLength(length: 8)]
public string $password;
#[Route(path: '/users/{id}', methods: ['GET', 'DELETE'])]
public function getUser(int $id) {
// ... logica per ottenere l'utente
}
}
// Come leggere gli Attributes in runtime (esempio con Reflection)
$reflectionClass = new ReflectionClass(UserRequest::class);
// Leggere gli Attributes di una proprietà
$property = $reflectionClass->getProperty('username');
$attributes = $property->getAttributes(MinLength::class);
foreach ($attributes as $attribute) {
$minLengthAttribute = $attribute->newInstance();
echo "Proprietà '{$property->getName()}' richiede una lunghezza minima di: {$minLengthAttribute->length}\
";
}
// Leggere gli Attributes di un metodo
$method = $reflectionClass->getMethod('getUser');
$routeAttributes = $method->getAttributes(Route::class);
foreach ($routeAttributes as $attribute) {
$route = $attribute->newInstance();
echo "Metodo '{$method->getName()}' è mappato al percorso: {$route->path} con metodi: " . implode(', ', $route->methods) . "\
";
}
Questo esempio mostra come definire e utilizzare gli Attributes per scopi di validazione e routing. La Reflection API ti permette di accedere a queste informazioni in fase di esecuzione, rendendo possibile costruire framework o librerie molto potenti e flessibili che reagiscono ai metadati nel tuo codice.
Gli Attributes sono un modo elegante e nativo per separare le preoccupazioni: il tuo codice si occupa della logica, mentre gli Attributes si occupano di metadati che possono essere usati da altre parti della tua applicazione (come un router o un validatore).
4. Constructor Property Promotion: Meno Boilerplate, Più Chiarezza
Se hai mai scritto classi in PHP, avrai sicuramente familiarità con il pattern di definire proprietà, poi un costruttore che prende parametri e li assegna alle proprietà. Questo crea molto codice "boilerplate" (ripetitivo) che è necessario ma non aggiunge molta logica significativa.
Prima di PHP 8, un costruttore tipico era:
class Product
{
public string $name;
public float $price;
public int $quantity;
public function __construct(string $name, float $price, int $quantity)
{
$this->name = $name;
$this->price = $price;
$this->quantity = $quantity;
}
}
$product = new Product('Laptop', 1200.50, 5);
echo "Prodotto: {$product->name}, Prezzo: {$product->price}, Quantità: {$product->quantity}\
";
Con Constructor Property Promotion in PHP 8, puoi combinare la definizione della proprietà e l'assegnazione nel costruttore in un'unica riga, riducendo drasticamente il codice ripetitivo.
class ProductV2
{
public function __construct(
public string $name,
public float $price,
public int $quantity
) {}
}
$productV2 = new ProductV2('Smartphone', 899.99, 10);
echo "Prodotto V2: {$productV2->name}, Prezzo: {$productV2->price}, Quantità: {$productV2->quantity}\
";
Come puoi vedere, il codice è molto più conciso e leggibile. PHP si occuperà automaticamente di definire le proprietà con i modificatori di visibilità specificati (public, protected, private) e di assegnare i valori dei parametri del costruttore a queste proprietà.
Vantaggi:
- Codice più pulito: Meno righe da scrivere e mantenere.
- Migliore leggibilità: L'intento della classe è più chiaro a colpo d'occhio.
- Meno errori: Meno possibilità di errori di battitura o di dimenticare un'assegnazione.
Questa funzionalità è particolarmente utile per le classi di tipo "value object" o "data transfer object" (DTO) che esistono principalmente per contenere dati.
5. Match Expression: Un'Alternativa Potente a Switch
L'istruzione switch è stata a lungo un pilastro per la gestione di diramazioni condizionali multiple. Tuttavia, switch ha alcune limitazioni e peculiarità, come la necessità di break; per evitare il "fall-through" e il fatto che non è un'espressione (non restituisce un valore).
PHP 8 introduce la Match Expression, che risolve queste problematiche e offre un'alternativa più moderna e sicura.
Differenze Chiave tra Match e Switch
- È un'Espressione:
matchrestituisce un valore, il che significa che puoi assegnare il suo risultato a una variabile o usarlo direttamente in un'espressione. - No Fall-Through: Non c'è bisogno di
break;. Ogni ramo esegue solo il suo blocco di codice e poi l'espressionematchtermina. - Strict Comparison:
matchusa la comparazione stretta (===) invece della comparazione debole (==) diswitch. Questo previene conversioni di tipo inaspettate. - Unhandled Cases: Se nessun ramo corrisponde e non c'è una clausola
default,matchlancerà unUnhandledMatchError. Questo ti aiuta a non dimenticare casi.
Esempio di Match Expression
Consideriamo un esempio in cui vogliamo determinare il tipo di messaggio basato su un codice di stato.
Con switch (pre-PHP 8):
$statusCode = 200;
$message = '';
switch ($statusCode) {
case 200:
$message = 'OK';
break;
case 404:
case 400:
$message = 'Errore cliente';
break;
case 500:
$message = 'Errore server';
break;
default:
$message = 'Sconosciuto';
break;
}
echo "Switch Message: {$message}\
";
Con match (PHP 8):
$statusCode = 200;
$message = match ($statusCode) {
200 => 'OK',
404, 400 => 'Errore cliente',
500 => 'Errore server',
default => 'Sconosciuto',
};
echo "Match Message: {$message}\
";
// Esempio di strict comparison
$value = '1';
$result = match ($value) {
1 => 'Numero 1',
'1' => 'Stringa 1',
default => 'Altro'
};
echo "Strict Match Result: {$result}\
"; // Output: Stringa 1
// Esempio di UnhandledMatchError
// $value = 99;
// $result = match ($value) {
// 1 => 'Uno'
// };
// Questo lancerebbe un UnhandledMatchError perché 99 non è gestito e non c'è default.
La Match Expression è più compatta, più sicura grazie alla comparazione stretta e al controllo dei casi non gestiti, e più funzionale perché restituisce un valore. È un'ottima aggiunta per rendere il codice PHP più moderno e robusto.
6. Nullsafe Operator: Semplificare i Controlli Null
Se hai mai lavorato con oggetti in PHP, avrai sicuramente incontrato la situazione in cui devi accedere a una proprietà o chiamare un metodo su un oggetto che potrebbe essere null. Questo spesso porta a catene di controlli if o all'uso dell'operatore ternario, che possono diventare ingombranti e poco leggibili.
Prima di PHP 8, potresti scrivere qualcosa del genere:
class User {
public ?Address $address = null;
}
class Address {
public ?string $street = null;
}
function getUserStreet(User $user): ?string {
if ($user->address !== null) {
return $user->address->street;
}
return null;
}
$userWithAddress = new User();
$userWithAddress->address = new Address();
$userWithAddress->address->street = 'Via Roma 10';
$userWithoutAddress = new User();
echo "Via utente con indirizzo: " . getUserStreet($userWithAddress) . "\
";
echo "Via utente senza indirizzo: " . (getUserStreet($userWithoutAddress) ?? 'Nessuna') . "\
";
// Ancora più annidato:
// $countryName = $user->address !== null ? ($user->address->country !== null ? $user->address->country->name : null) : null;
Il Nullsafe Operator (?->) in PHP 8 ti permette di concatenare chiamate a metodi e accessi a proprietà, e se uno qualsiasi degli elementi della catena è null, l'intera espressione valuta a null e l'esecuzione si ferma, senza lanciare un TypeError.
class UserV2 {
public ?AddressV2 $address = null;
}
class AddressV2 {
public ?string $street = null;
public ?CityV2 $city = null;
}
class CityV2 {
public ?string $name = null;
}
function getUserStreetV2(UserV2 $user): ?string {
// Con Nullsafe Operator: se $user->address è null, l'intera espressione è null
return $user->address?->street;
}
$userWithAddressV2 = new UserV2();
$userWithAddressV2->address = new AddressV2();
$userWithAddressV2->address->street = 'Via Garibaldi 20';
$userWithoutAddressV2 = new UserV2();
echo "Via utente V2 con indirizzo: " . getUserStreetV2($userWithAddressV2) . "\
";
echo "Via utente V2 senza indirizzo: " . (getUserStreetV2($userWithoutAddressV2) ?? 'Nessuna') . "\
";
// Catena più lunga con Nullsafe Operator
$userWithFullAddress = new UserV2();
$userWithFullAddress->address = new AddressV2();
$userWithFullAddress->address->city = new CityV2();
$userWithFullAddress->address->city->name = 'Milano';
$cityName = $userWithFullAddress->address?->city?->name;
echo "Nome città (con indirizzo completo): {$cityName}\
";
$userWithPartialAddress = new UserV2();
$userWithPartialAddress->address = new AddressV2(); // address esiste, ma city è null
$cityNamePartial = $userWithPartialAddress->address?->city?->name;
echo "Nome città (con indirizzo parziale): " . ($cityNamePartial ?? 'Sconosciuta') . "\
";
Il Nullsafe Operator rende il codice molto più pulito e meno propenso a errori quando si lavora con strutture di dati potenzialmente nulle. È un piccolo cambiamento sintattico con un grande impatto sulla leggibilità e la robustezza del codice.
7. Union Types (Tipi Unione): Più Flessibilità nella Tipizzazione
PHP ha introdotto la tipizzazione (type hints) per i parametri e i valori di ritorno delle funzioni/metodi nelle versioni precedenti, migliorando la robustezza e la chiarezza del codice. Tuttavia, a volte un parametro o un valore di ritorno potrebbe legittimamente accettare o restituire più di un tipo.
Prima di PHP 8, per indicare che un parametro potesse essere di tipi diversi, dovevi ricorrere a PHPDoc (commenti di documentazione) che non erano applicati dal motore PHP, o a soluzioni più complesse.
// Pre-PHP 8: Usando PHPDoc per indicare tipi multipli (non enforced dal motore)
/**
* @param int|string $id
*/
function processId($id) {
if (is_int($id)) {
echo "ID numerico: {$id}\
";
} elseif (is_string($id)) {
echo "ID stringa: {$id}\
";
}
}
processId(123);
processId('abc');
Con i Union Types in PHP 8, puoi specificare che una variabile può essere di uno tra diversi tipi, usando il simbolo |.
function processIdV2(int|string $id) {
if (is_int($id)) {
echo "ID numerico V2: {$id}\
";
} elseif (is_string($id)) {
echo "ID stringa V2: {$id}\
";
}
}
processIdV2(456);
processIdV2('def');
// Questo causerebbe un TypeError in fase di esecuzione
// processIdV2(true);
class MyClass {}
class MyOtherClass {}
function handleData(string|int|float|MyClass $data) {
if (is_object($data)) {
echo "Dato di tipo MyClass.\
";
} else {
echo "Dato primitivo: {$data} ({gettype($data)})\
";
}
}
handleData('test');
handleData(123);
handleData(3.14);
handleData(new MyClass());
// handleData(new MyOtherClass()); // Questo lancerebbe un TypeError
Vantaggi dei Union Types:
- Tipizzazione più precisa: Puoi esprimere intenti più complessi sulla tipologia dei dati.
- Robustezza: Il motore PHP applica questi tipi, rilevando errori prima.
- Auto-documentazione: Il codice diventa più chiaro e auto-esplicativo.
È importante notare che null può essere incluso come parte di un tipo unione, ad esempio string|null è equivalente a ?string. Tuttavia, non puoi combinare void con altri tipi, poiché void significa "nessun valore di ritorno".
8. Nuove Funzioni Stringa: Semplicità e Consistenza
PHP 8 introduce tre nuove funzioni per la manipolazione delle stringhe che sono state a lungo richieste e che rendono il codice più pulito ed efficiente rispetto alle alternative precedenti.
str_contains()
Questa funzione verifica se una stringa contiene un'altra sottostringa. Prima, dovevi usare strpos() e controllare se il risultato non fosse false, il che era un po' macchinoso e meno leggibile.
$haystack = 'Questa è una frase di esempio.';
// Pre-PHP 8:
if (strpos($haystack, 'frase') !== false) {
echo "'frase' trovata (vecchio metodo).\
";
}
// PHP 8:
if (str_contains($haystack, 'frase')) {
echo "'frase' trovata (nuovo metodo).\
";
}
if (!str_contains($haystack, 'parola')) {
echo "'parola' non trovata (nuovo metodo).\
";
}
str_starts_with()
Verifica se una stringa inizia con una data sottostringa. Prima, si usava substr() o strpos() con un controllo sull'indice 0.
$text = 'Benvenuti in PHP 8!';
// Pre-PHP 8:
if (strpos($text, 'Benvenuti') === 0) {
echo "Il testo inizia con 'Benvenuti' (vecchio metodo).\
";
}
// PHP 8:
if (str_starts_with($text, 'Benvenuti')) {
echo "Il testo inizia con 'Benvenuti' (nuovo metodo).\
";
}
str_ends_with()
Verifica se una stringa termina con una data sottostringa. Richiedeva substr() e calcoli di lunghezza.
$filename = 'documento.pdf';
// Pre-PHP 8: (esempio semplificato, spesso più complesso)
if (substr($filename, -4) === '.pdf') {
echo "Il file è un PDF (vecchio metodo).\
";
}
// PHP 8:
if (str_ends_with($filename, '.pdf')) {
echo "Il file è un PDF (nuovo metodo).\
";
}
Queste tre funzioni migliorano notevolmente la leggibilità e la chiarezza del codice quando si eseguono operazioni comuni sulle stringhe. Sono semplici, dirette e riducono la complessità che altrimenti sarebbe necessaria.
9. Altre Novità e Miglioramenti Degni di Nota
Oltre alle funzionalità principali che abbiamo esplorato, PHP 8 ha introdotto una miriade di altri miglioramenti e cambiamenti che contribuiscono a renderlo un linguaggio più moderno e performante.
Weak Maps
Le WeakMap sono una nuova struttura dati che permette di memorizzare oggetti come chiavi senza impedire che questi oggetti vengano "garbage-collected" (rimossi dalla memoria) quando non sono più referenziati altrove. Questo è utile per la memorizzazione nella cache o per associazioni di dati temporanee che non devono impedire la liberazione di memoria.
$obj = new stdClass();
$map = new WeakMap();
$map[$obj] = 'Dati associati';
var_dump($map);
unset($obj); // L'oggetto $obj viene dereferenziato
// Dopo il garbage collection, $obj non sarà più nella WeakMap
// (Il GC in PHP non è deterministico, quindi potrebbe non essere immediato)
var_dump($map);
Throw Expression
Ora puoi usare throw come espressione in PHP 8, il che significa che puoi lanciarlo in contesti dove prima era permesso solo un'espressione, come operatori ternari o l'operatore di coalescenza null (??).
$user = null;
$name = $user?->name ?? throw new Exception('Utente non trovato!');
echo "Nome: {$name}\
"; // Se $user è null, lancia l'eccezione
Miglioramenti alla Reflection API
La Reflection API è stata aggiornata per supportare le nuove funzionalità come gli Attributes e i Union Types, permettendo ai framework e alle librerie di sfruttare appieno queste novità.
Nuove Interfacce per Contenuti Stringabili
Stringable è una nuova interfaccia che viene implementata implicitamente da tutte le classi che hanno un metodo __toString(). Questo permette di usare string|Stringable come tipo unione per indicare che un parametro accetta sia stringhe che oggetti che possono essere convertiti in stringa.
Deprecazioni e Cambiamenti Minori
Come ogni major release, PHP 8 ha anche deprecato alcune funzionalità obsolete e introdotto modifiche minori per pulire il linguaggio e prepararlo per il futuro. È sempre una buona pratica consultare la documentazione ufficiale per un elenco completo delle deprecazioni quando si esegue l'upgrade.
Esempi Pratici e Casi d'Uso Reali
Vediamo come alcune di queste funzionalità possono essere combinate in scenari reali per scrivere codice più efficiente e manutenibile.
Esempio 1: Un Controller API con Attributes e Named Arguments
Immagina di costruire un'API REST. Potresti usare gli Attributes per definire i percorsi e i metodi HTTP direttamente nei tuoi controller, e i Named Arguments per rendere le chiamate a funzioni di servizio più chiare.
#[Attribute(Attribute::TARGET_METHOD)]
class ApiRoute
{
public function __construct(public string $path, public array $methods = ['GET']) {}
}
interface UserRepositoryInterface {
public function find(int $id): ?array;
public function save(array $userData): array;
}
class InMemoryUserRepository implements UserRepositoryInterface {
private array $users = [];
private int $nextId = 1;
public function find(int $id): ?array {
return $this->users[$id] ?? null;
}
public function save(array $userData): array {
if (!isset($userData['id'])) {
$userData['id'] = $this->nextId++;
}
$this->users[$userData['id']] = $userData;
return $userData;
}
}
class UserController
{
public function __construct(private UserRepositoryInterface $userRepository) {}
#[ApiRoute(path: '/api/users/{id}', methods: ['GET'])]
public function getUserById(int $id): void
{
$user = $this->userRepository->find($id);
if ($user === null) {
http_response_code(404);
echo json_encode(['message' => 'Utente non trovato']);
return;
}
echo json_encode($user);
}
#[ApiRoute(path: '/api/users', methods: ['POST'])]
public function createUser(array $requestData):
// Qui potremmo usare Union Types per $requestData se venisse da diverse fonti
void
{
// Utilizzo dei Named Arguments per chiarezza se la funzione save avesse molti parametri
$newUser = $this->userRepository->save(
userData: [
'name' => $requestData['name'] ?? null,
'email' => $requestData['email'] ?? null
]
);
http_response_code(201);
echo json_encode($newUser);
}
}
// Questo è solo un esempio per mostrare gli attributes e i named arguments.
// Un framework reale userebbe la Reflection API per leggere gli ApiRoute e instradare le richieste.
// Per simulare l'uso:
$userRepo = new InMemoryUserRepository();
$controller = new UserController($userRepo);
// Simuliamo una richiesta POST
$postData = ['name' => 'Alice', 'email' => 'alice@example.com'];
$controller->createUser($postData);
// Simuliamo una richiesta GET (assumendo che l'ID 1 sia stato creato)
// In un'applicazione reale, l'ID verrebbe estratto dall'URL
// $controller->getUserById(1);
// Output: {"id":1,"name":"Alice","email":"alice@example.com"}
Questo esempio mostra come gli Attributes possono rendere i controller più dichiarativi e come i Named Arguments migliorano la leggibilità delle chiamate a metodi di servizio.
Esempio 2: Gestione di Stati Complessi con Match Expression e Constructor Property Promotion
Supponiamo di avere un sistema di gestione degli ordini e che ogni ordine possa avere diversi stati, ognuno con una logica leggermente diversa per la visualizzazione o l'elaborazione.
class OrderStatus {
public const PENDING = 'pending';
public const PROCESSING = 'processing';
public const SHIPPED = 'shipped';
public const DELIVERED = 'delivered';
public const CANCELLED = 'cancelled';
}
// Usiamo Constructor Property Promotion per una classe semplice
class OrderItem
{
public function __construct(
public string $productName,
public int $quantity,
public float $price
) {}
}
class Order
{
public function __construct(
public int $id,
public string $status,
public array $items,
public ?string $trackingNumber = null
) {}
public function getStatusDescription(): string
{
return match ($this->status) {
OrderStatus::PENDING => 'In attesa di elaborazione',
OrderStatus::PROCESSING => 'In fase di preparazione',
OrderStatus::SHIPPED => 'Spedito' . ($this->trackingNumber ? " (Tracking: {$this->trackingNumber})" : ''),
OrderStatus::DELIVERED => 'Consegnato',
OrderStatus::CANCELLED => 'Annullato',
default => throw new InvalidArgumentException("Stato ordine sconosciuto: {$this->status}"),
};
}
public function getTotalAmount(): float
{
$total = 0.0;
foreach ($this->items as $item) {
$total += $item->quantity * $item->price;
}
return $total;
}
}
$orderItems = [
new OrderItem(productName: 'Libro PHP 8', quantity: 1, price: 35.99),
new OrderItem(productName: 'Tazza PHP', quantity: 2, price: 9.50)
];
$order1 = new Order(
id: 101,
status: OrderStatus::PROCESSING,
items: $orderItems
);
$order2 = new Order(
id: 102,
status: OrderStatus::SHIPPED,
items: $orderItems,
trackingNumber: 'TRK12345'
);
$order3 = new Order(
id: 103,
status: OrderStatus::DELIVERED,
items: $orderItems
);
echo "Ordine {$order1->id}: Stato: {$order1->getStatusDescription()}, Totale: {$order1->getTotalAmount()}\
";
echo "Ordine {$order2->id}: Stato: {$order2->getStatusDescription()}, Totale: {$order2->getTotalAmount()}\
";
echo "Ordine {$order3->id}: Stato: {$order3->getStatusDescription()}, Totale: {$order3->getTotalAmount()}\
";
// Esempio di unhandled case (se non ci fosse il default)
// $orderInvalid = new Order(id: 104, status: 'unknown_status', items: []);
// echo $orderInvalid->getStatusDescription(); // Lancia InvalidArgumentException
Qui vediamo come Constructor Property Promotion riduca il boilerplate per OrderItem e Order, e come Match Expression gestisca gli stati dell'ordine in modo pulito ed efficiente, restituendo direttamente una descrizione e gestendo i casi non previsti con un'eccezione.
Errori Comuni e Migliori Pratiche con PHP 8
Anche se PHP 8 introduce molte funzionalità che rendono il codice più sicuro, è importante essere consapevoli di alcuni errori comuni e seguire le migliori pratiche:
- Non Abusare del JIT: Non aspettarti che il JIT risolva tutti i problemi di performance. Per la maggior parte delle applicazioni web, l'ottimizzazione del codice, delle query database e della gestione dell'I/O avrà un impatto maggiore. Testa sempre le performance.
- Comprendere i Named Arguments: Sono fantastici per la leggibilità, ma se mescoli argomenti posizionali e nominati, ricorda che i posizionali devono venire prima. Evita di usarli per parametri obbligatori se la funzione è molto semplice e i nomi dei parametri sono ovvi.
- Attenzione agli Attributes: Possono essere molto potenti, ma usali con moderazione e solo quando aggiungono valore. Un abuso può rendere il codice difficile da seguire. Assicurati che le classi Attribute siano ben documentate.
matchvsswitch: Ricorda chematchusa la comparazione stretta (===). Questo è un vantaggio per la sicurezza, ma se ti aspetti una comparazione debole (==), dovrai adattare il tuo codice. Prevedi sempre undefaulto assicurati che tutti i casi possibili siano gestiti per evitareUnhandledMatchError.- Upgrade Graduale: Non affrettarti a migrare un intero progetto legacy a PHP 8 e ad adottare tutte le nuove funzionalità contemporaneamente. Procedi per gradi, testa a fondo e aggiorna il codice pezzo per pezzo. Fai attenzione alle deprecazioni.
- Tipizzazione Rigorosa: Con i Union Types e la tipizzazione delle proprietà, PHP 8 spinge verso un codice più tipizzato. Abilita la modalità
declare(strict_types=1);all'inizio dei tuoi file PHP per applicare una tipizzazione rigorosa e prevenire conversioni di tipo implicite.
Prossimi Passi e Risorse per Approfondire
Congratulazioni! Hai fatto un grande passo avanti nella comprensione delle novità di PHP 8. Ma il viaggio non finisce qui. La programmazione è un apprendimento continuo. Ecco alcuni suggerimenti per i tuoi prossimi passi:
- Sperimenta con il Codice: Il modo migliore per imparare è fare. Crea un nuovo progetto PHP 8 (o aggiorna un ambiente di sviluppo esistente) e prova a implementare le funzionalità che hai imparato. Scrivi piccole funzioni usando Named Arguments, Match Expressions, e crea le tue classi con Constructor Property Promotion.
- Consulta la Documentazione Ufficiale: La documentazione di PHP è una risorsa inestimabile. Ogni nuova funzionalità è descritta in dettaglio, spesso con esempi aggiuntivi. Visita
php.net. - Esplora i Framework: Framework come Laravel e Symfony stanno adottando attivamente le funzionalità di PHP 8. Studiare come li utilizzano ti darà una prospettiva su come integrare queste novità in applicazioni più grandi.
- Community e Blog: Segui blog di sviluppatori PHP, partecipa a forum e community online. Sarai sempre aggiornato sulle ultime tendenze e sulle migliori pratiche.
- Approfondisci Altri Argomenti: Ora che hai una solida base su PHP 8, potresti voler approfondire argomenti correlati come:
- PSR (PHP Standard Recommendations): Le linee guida per scrivere codice PHP di alta qualità e interoperabile.
- Test Unitari: Impara a scrivere test per il tuo codice PHP per garantirne la robustezza.
- Composer: Il gestore di dipendenze per PHP, essenziale per qualsiasi progetto moderno.
- Database e ORM: Come interagire con i database usando PHP e strumenti come Doctrine o Eloquent.
PHP 8 ha segnato un'era di rinnovamento per il linguaggio, rendendolo più performante, più espressivo e più piacevole da usare. Padroneggiare queste novità ti darà un vantaggio significativo nel tuo percorso di sviluppo web. Buona programmazione!