PHP: caratteristiche moderne
PHP ha subito una trasformazione profonda a partire dalla versione 7, accelerata ulteriormente con PHP 8. Quello che un tempo era considerato un linguaggio poco strutturato e privo di rigore sintattico si è evoluto in un ecosistema maturo, dotato di un sistema di tipi robusto, costrutti sintattici espressivi e funzionalità mutuate dai linguaggi più moderni. In questo articolo analizzeremo le caratteristiche che rendono PHP un linguaggio contemporaneo e competitivo.
Il sistema di tipi
Una delle aree in cui PHP ha compiuto i progressi più significativi è il sistema di tipi. Le versioni recenti hanno introdotto i tipi unione, i tipi intersezione e il tipo never, avvicinando PHP a linguaggi con tipizzazione forte come TypeScript.
I tipi unione, introdotti in PHP 8.0, permettono di dichiarare che un parametro o un valore di ritorno può essere di uno tra più tipi specificati.
// Funzione che accetta un intero o una stringa
function formatValue(int|string $value): string
{
if (is_int($value)) {
// Formattazione numerica con separatore delle migliaia
return number_format($value, 0, ',', '.');
}
// Restituzione della stringa senza modifiche
return $value;
}
echo formatValue(1500000); // 1.500.000
echo formatValue('Testo libero'); // Testo libero
I tipi intersezione, introdotti in PHP 8.1, permettono di richiedere che un valore implementi simultaneamente più interfacce.
interface Loggable
{
public function toLog(): string;
}
interface Serializable
{
public function serialize(): string;
}
// Il parametro deve implementare entrambe le interfacce
function processEntry(Loggable&Serializable $entry): void
{
// Registrazione nel log
error_log($entry->toLog());
// Serializzazione per l'archiviazione
file_put_contents('archive.dat', $entry->serialize());
}
Il tipo never, anch'esso introdotto in PHP 8.1, indica che una funzione non restituisce mai un valore perché termina sempre con un'eccezione o con l'interruzione dello script.
// Funzione che interrompe sempre l'esecuzione
function abortWithError(string $message): never
{
// Registrazione dell'errore e interruzione
error_log($message);
throw new RuntimeException($message);
}
Le enumerazioni
Le enumerazioni, introdotte in PHP 8.1, rappresentano una delle aggiunte più attese. A differenza delle semplici costanti, le enum sono tipi a tutti gli effetti, supportano metodi e possono implementare interfacce. PHP distingue tra enum pure e backed enum, queste ultime associate a valori scalari.
// Enumerazione con valori stringa associati
enum OrderStatus: string
{
case Pending = 'pending';
case Confirmed = 'confirmed';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
// Metodo per verificare se l'ordine è ancora attivo
public function isActive(): bool
{
return match ($this) {
self::Pending, self::Confirmed, self::Shipped => true,
self::Delivered, self::Cancelled => false,
};
}
// Etichetta leggibile per l'interfaccia utente
public function label(): string
{
return match ($this) {
self::Pending => 'In attesa',
self::Confirmed => 'Confermato',
self::Shipped => 'Spedito',
self::Delivered => 'Consegnato',
self::Cancelled => 'Annullato',
};
}
}
$status = OrderStatus::Shipped;
echo $status->label(); // Spedito
echo $status->isActive() ? 'Attivo' : 'Chiuso'; // Attivo
// Ricostruzione da valore stringa proveniente dal database
$fromDb = OrderStatus::from('confirmed');
L'espressione match
L'espressione match, introdotta in PHP 8.0, è un'evoluzione di switch che utilizza il confronto strict (===), non richiede break e restituisce un valore. La sua natura di espressione la rende utilizzabile ovunque sia atteso un valore.
// Calcolo del costo di spedizione in base alla zona
function calculateShipping(string $zone, float $weight): float
{
// Tariffa base per zona geografica
$baseRate = match ($zone) {
'local' => 5.00,
'national' => 10.00,
'europe' => 25.00,
'worldwide' => 50.00,
default => throw new InvalidArgumentException("Zona non valida: {$zone}"),
};
// Supplemento per peso superiore a 5 kg
$weightSurcharge = match (true) {
$weight <= 5 => 0,
$weight <= 20 => $baseRate * 0.5,
$weight <= 50 => $baseRate * 1.0,
default => $baseRate * 2.0,
};
return $baseRate + $weightSurcharge;
}
Gli argomenti con nome
Gli argomenti con nome, introdotti in PHP 8.0, permettono di passare i parametri in base al nome anziché alla posizione. Questo migliora la leggibilità del codice, specialmente con funzioni che accettano molti parametri opzionali.
// Configurazione di una connessione al database
function createConnection(
string $host,
int $port = 3306,
string $database = '',
string $charset = 'utf8mb4',
int $timeout = 30,
bool $persistent = false,
bool $ssl = false
): PDO {
$dsn = "mysql:host={$host};port={$port};dbname={$database};charset={$charset}";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => $timeout,
PDO::ATTR_PERSISTENT => $persistent,
];
return new PDO($dsn, options: $options);
}
// Uso con argomenti con nome: chiaro e leggibile
$db = createConnection(
host: 'db.example.com',
database: 'app_production',
ssl: true,
timeout: 10,
);
Le fibre
Le fibre (Fibers), introdotte in PHP 8.1, forniscono un meccanismo di concorrenza cooperativa. Una fibra può sospendere la propria esecuzione e restituire il controllo al chiamante, per poi riprendere dal punto esatto in cui si era interrotta. Questo modello è alla base di librerie asincrone come ReactPHP e Amp.
// Creazione di una fibra che simula un'operazione lunga
$fiber = new Fiber(function (): void {
$steps = ['Connessione', 'Autenticazione', 'Trasferimento', 'Verifica'];
foreach ($steps as $index => $step) {
// Sospensione con restituzione dello stato corrente
$result = Fiber::suspend([
'step' => $step,
'progress' => ($index + 1) / count($steps) * 100,
]);
// Ricezione del segnale dal chiamante
echo "Ricevuto dal chiamante: {$result}\n";
}
});
// Avvio della fibra
$state = $fiber->start();
echo "{$state['step']}: {$state['progress']}%\n";
// Ripresa della fibra con un valore
while ($fiber->isSuspended()) {
$state = $fiber->resume('continua');
if ($state !== null) {
echo "{$state['step']}: {$state['progress']}%\n";
}
}
Le proprietà readonly e le classi readonly
Le proprietà readonly, introdotte in PHP 8.1, possono essere inizializzate una sola volta e non possono essere modificate successivamente. PHP 8.2 ha esteso questo concetto alle classi, rendendo tutte le proprietà di una classe automaticamente readonly.
// Classe readonly: tutte le proprietà sono immutabili
readonly class Money
{
public function __construct(
public float $amount,
public string $currency,
) {}
// Operazione che restituisce una nuova istanza
public function add(Money $other): self
{
if ($this->currency !== $other->currency) {
throw new InvalidArgumentException('Valute diverse non sommabili');
}
// Nuova istanza perché le proprietà sono immutabili
return new self($this->amount + $other->amount, $this->currency);
}
public function format(): string
{
return number_format($this->amount, 2, ',', '.') . ' ' . $this->currency;
}
}
$price = new Money(29.99, 'EUR');
$tax = new Money(6.60, 'EUR');
$total = $price->add($tax);
echo $total->format(); // 36,59 EUR
La promozione delle proprietà nel costruttore
La promozione delle proprietà nel costruttore, introdotta in PHP 8.0, elimina la necessità di dichiarare separatamente le proprietà e di assegnarle nel corpo del costruttore. Questa sintassi riduce drasticamente il codice boilerplate nelle classi di tipo DTO e value object.
// Classe con promozione delle proprietà
class UserProfile
{
public function __construct(
private readonly int $id,
private string $displayName,
private string $email,
private array $roles = [],
private ?DateTimeImmutable $lastLogin = null,
) {}
// Verifica del ruolo
public function hasRole(string $role): bool
{
return in_array($role, $this->roles, true);
}
// Aggiornamento dell'ultimo accesso
public function updateLastLogin(): void
{
$this->lastLogin = new DateTimeImmutable();
}
// Rappresentazione come array associativo
public function toArray(): array
{
return [
'id' => $this->id,
'display_name' => $this->displayName,
'email' => $this->email,
'roles' => $this->roles,
'last_login' => $this->lastLogin?->format('Y-m-d H:i:s'),
];
}
}
Gli attributi
Gli attributi, introdotti in PHP 8.0, forniscono un meccanismo nativo per aggiungere metadati strutturati a classi, metodi, proprietà e parametri. Sostituiscono le annotazioni nei commenti DocBlock con una sintassi formale verificabile a livello di linguaggio.
#[Attribute(Attribute::TARGET_METHOD)]
class Route
{
public function __construct(
public readonly string $path,
public readonly string $method = 'GET',
) {}
}
#[Attribute(Attribute::TARGET_CLASS)]
class Controller
{
public function __construct(
public readonly string $prefix = '',
) {}
}
#[Controller(prefix: '/api/users')]
class UserController
{
#[Route(path: '/', method: 'GET')]
public function index(): array
{
// Elenco degli utenti
return ['users' => []];
}
#[Route(path: '/{id}', method: 'GET')]
public function show(int $id): array
{
// Dettaglio di un singolo utente
return ['user' => ['id' => $id]];
}
#[Route(path: '/', method: 'POST')]
public function store(): array
{
// Creazione di un nuovo utente
return ['created' => true];
}
}
// Lettura degli attributi tramite Reflection
function registerRoutes(string $controllerClass): void
{
$reflector = new ReflectionClass($controllerClass);
// Recupero del prefisso dal controller
$controllerAttrs = $reflector->getAttributes(Controller::class);
$prefix = $controllerAttrs[0]?->newInstance()->prefix ?? '';
foreach ($reflector->getMethods() as $method) {
$routeAttrs = $method->getAttributes(Route::class);
foreach ($routeAttrs as $attr) {
$route = $attr->newInstance();
$fullPath = $prefix . $route->path;
// Registrazione della rotta nel sistema
echo "{$route->method} {$fullPath} => {$method->getName()}\n";
}
}
}
registerRoutes(UserController::class);
Le funzioni freccia e le closure
Le funzioni freccia, introdotte in PHP 7.4, offrono una sintassi compatta per le closure a singola espressione. A differenza delle closure tradizionali, le funzioni freccia catturano automaticamente le variabili dallo scope esterno senza la necessità della clausola use.
// Pipeline di trasformazione dati con funzioni freccia
class DataPipeline
{
private array $operations = [];
// Aggiunta di un'operazione alla pipeline
public function pipe(Closure $operation): self
{
$this->operations[] = $operation;
return $this;
}
// Esecuzione di tutte le operazioni in sequenza
public function process(array $data): array
{
return array_reduce(
$this->operations,
fn(array $carry, Closure $op) => $op($carry),
$data
);
}
}
$minPrice = 10.0;
$taxRate = 0.22;
$result = (new DataPipeline())
// Filtraggio dei prodotti con prezzo minimo
->pipe(fn(array $items) => array_filter(
$items,
fn(array $item) => $item['price'] >= $minPrice
))
// Applicazione dell'IVA
->pipe(fn(array $items) => array_map(
fn(array $item) => [
...$item,
'price_with_tax' => $item['price'] * (1 + $taxRate),
],
$items
))
// Ordinamento per prezzo decrescente
->pipe(function (array $items) {
usort($items, fn($a, $b) => $b['price'] <=> $a['price']);
return $items;
})
->process($products);
L'operatore nullsafe
L'operatore nullsafe (?->), introdotto in PHP 8.0, permette di concatenare chiamate a metodi e accessi a proprietà senza verificare manualmente la nullità ad ogni passaggio. Se un elemento della catena è null, l'intera espressione restituisce null senza generare errori.
class Address
{
public function __construct(
public readonly string $city,
public readonly ?string $zipCode = null,
) {}
}
class Company
{
public function __construct(
public readonly string $name,
public readonly ?Address $headquarters = null,
) {}
}
class Employee
{
public function __construct(
public readonly string $fullName,
public readonly ?Company $company = null,
) {}
}
// Senza operatore nullsafe: verboso e ripetitivo
function getCityOldWay(Employee $employee): ?string
{
if ($employee->company === null) {
return null;
}
if ($employee->company->headquarters === null) {
return null;
}
return $employee->company->headquarters->city;
}
// Con operatore nullsafe: conciso e leggibile
function getCity(Employee $employee): ?string
{
// Navigazione sicura attraverso la catena di oggetti
return $employee->company?->headquarters?->city;
}
// Funziona anche con le chiamate ai metodi
$zipCode = $employee->company?->headquarters?->zipCode;
Le espressioni di prima classe per i callable
PHP 8.1 ha introdotto la sintassi di prima classe per i callable, che permette di creare una closure a partire da qualsiasi funzione o metodo usando l'operatore (...). Questo elimina la necessità di usare stringhe o array per riferirsi ai callable.
// Riferimento a funzioni native come closure
$trimmed = array_map(trim(...), [' ciao ', ' mondo ']);
// Riferimento a metodi statici
class Validator
{
public static function isNonEmpty(string $value): bool
{
// Verifica che la stringa non sia vuota dopo il trim
return trim($value) !== '';
}
public static function isEmail(string $value): bool
{
// Validazione dell'indirizzo email
return filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
}
// Composizione di validatori come closure di prima classe
$validators = [
Validator::isNonEmpty(...),
Validator::isEmail(...),
];
function validateAll(string $input, array $validators): bool
{
foreach ($validators as $validate) {
if (!$validate($input)) {
return false;
}
}
return true;
}
$isValid = validateAll('user@example.com', $validators); // true
Lo spread operator e il destructuring
Lo spread operator (...) e le tecniche di destructuring rendono la manipolazione di array e argomenti più espressiva e funzionale. Lo spread operator funziona sia negli argomenti delle funzioni sia nella composizione di array.
// Composizione di configurazioni con lo spread operator
function mergeConfig(array $defaults, array ...$overrides): array
{
$result = $defaults;
foreach ($overrides as $override) {
// Sovrascrittura progressiva delle impostazioni
$result = [...$result, ...$override];
}
return $result;
}
$baseConfig = ['debug' => false, 'cache' => true, 'locale' => 'it_IT'];
$envConfig = ['debug' => true, 'log_level' => 'verbose'];
$userConfig = ['locale' => 'en_US'];
$finalConfig = mergeConfig($baseConfig, $envConfig, $userConfig);
// Destructuring con list() e sintassi breve
$coordinates = [45.3271, 14.4422, 'Pescara'];
[$latitude, $longitude, $city] = $coordinates;
// Destructuring di array associativi con list() e chiavi
$response = ['status' => 200, 'body' => '{"ok":true}', 'headers' => []];
['status' => $statusCode, 'body' => $body] = $response;
Le named arguments nelle funzioni native
Gli argomenti con nome non si limitano alle funzioni definite dall'utente: sono utilizzabili anche con le funzioni native di PHP, migliorando la leggibilità di chiamate che in precedenza richiedevano il passaggio esplicito di tutti i parametri posizionali per raggiungere quello desiderato.
// Senza argomenti con nome: parametri posizionali poco chiari
$result = array_slice($items, 0, 10, true);
// Con argomenti con nome: chiaro e autoesplicativo
$result = array_slice($items, offset: 0, length: 10, preserve_keys: true);
// Formattazione di numeri con parametri espliciti
$formatted = number_format(
num: 1234567.891,
decimals: 2,
decimal_separator: ',',
thousands_separator: '.',
);
// Creazione di un cookie con parametri leggibili
setcookie(
name: 'session_id',
value: $token,
expires_or_options: [
'expires' => time() + 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict',
],
);
Le costanti tipizzate nelle classi
PHP 8.3 ha introdotto la possibilità di dichiarare il tipo delle costanti nelle classi e nelle interfacce. Questo aggiunge un ulteriore livello di sicurezza, impedendo che le classi derivate ridefiniscano una costante con un tipo incompatibile.
interface CacheDriver
{
// Costante tipizzata: le implementazioni devono rispettare il tipo
const int DEFAULT_TTL = 3600;
const string PREFIX = 'cache_';
}
class RedisCache implements CacheDriver
{
// Sovrascrittura valida: stesso tipo intero
const int DEFAULT_TTL = 7200;
public function get(string $key): mixed
{
$prefixedKey = self::PREFIX . $key;
// Recupero del valore dalla cache Redis
return null;
}
}
Il deep cloning con clone()
PHP 8.4 introduce la possibilità di modificare le proprietà di un oggetto durante la clonazione, consentendo di sovrascrivere proprietà readonly nella nuova istanza. Questa funzionalità semplifica notevolmente i pattern immutabili.
readonly class EventRecord
{
public function __construct(
public string $type,
public string $payload,
public DateTimeImmutable $occurredAt,
public ?string $processedBy = null,
) {}
// Creazione di una copia con il campo di elaborazione aggiornato
public function markProcessed(string $processor): self
{
// Clonazione con modifica della proprietà readonly
return clone $this with {
processedBy: $processor,
};
}
}
$event = new EventRecord('order.created', '{"id":42}', new DateTimeImmutable());
$processed = $event->markProcessed('worker-1');
I property hooks
PHP 8.4 introduce i property hooks, che permettono di definire getter e setter direttamente nella dichiarazione della proprietà, eliminando la necessità di metodi separati. Questo avvicina PHP al modello delle computed properties presente in altri linguaggi.
class Temperature
{
// Proprietà con hook get e set
public float $celsius {
get => $this->celsius;
set(float $value) {
if ($value < -273.15) {
throw new InvalidArgumentException('Sotto lo zero assoluto');
}
$this->celsius = $value;
}
}
// Proprietà virtuale derivata: solo getter
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set(float $value) {
// Conversione e assegnazione in Celsius
$this->celsius = ($value - 32) * 5 / 9;
}
}
public function __construct(float $celsius)
{
$this->celsius = $celsius;
}
}
$temp = new Temperature(100);
echo $temp->fahrenheit; // 212
$temp->fahrenheit = 32;
echo $temp->celsius; // 0
Conclusioni
PHP ha compiuto un percorso di modernizzazione che lo ha reso un linguaggio espressivo, tipizzato e dotato di costrutti sintattici sofisticati. Il sistema di tipi, le enumerazioni, le fibre, gli attributi, le proprietà readonly, i property hooks e le numerose migliorie sintattiche dimostrano una direzione chiara verso un linguaggio sempre più rigoroso e aderente ai principi della programmazione moderna. Oggi scrivere PHP significa disporre di strumenti che favoriscono la sicurezza del codice, l'immutabilità dei dati e la chiarezza espressiva, qualità che lo rendono una scelta valida e competitiva per progetti di ogni scala.