Tanti anni fa, quando ero agli inizi della mia carriera da programmatore, lessi su una rivista eccezionale, DEV, un'intervista a Bjarne Stroustrup. Mi colpì una frase:
Match Expression
Finalmente PHP ci regala un costrutto moderno e più sintetico:
<?php
$commissione = match ($metodoPagamento) {
'bonifico' => 1.50,
'paypal' => 0.75,
default => 0,
};
Default è una parola chiave (non va messo tra apici) e non è obbligatorio. Se però viene omesso e la funzione riscontra un valore non presente nell'elenco, ritorna un'eccezione di tipo UnhandledMatchError.
Alla fine non è che questo costrutto risolva tutti i problemi (ogni nuovo possibile valore è comunque un'aggiunta di codice) ma la sintassi è più robusta ed elegante.
Polimorfismo avanzato
Proviamo a risolvere il problema degli if/switch eseguendo un salto concettuale e spostandoci sugli oggetti.
Iniziamo dichiarando il comportamento necessario attraverso un'interfaccia, che definisce il contratto del comportamento:
<?php
interface MetodoPagamento
{
public function getCommissione(): float;
}
Implementiamo con le classi:
<?php
class Bonifico implements MetodoPagamento
{
public function getCommissione(): float
{
return 1.50;
}
}
<?php
class Paypal implements MetodoPagamento
{
public function getCommissione(): float
{
return 0.75;
}
}
e finalmente utilizziamo la nostra interfaccia:
<?php
function checkout(MetodoPagamento $metodoPagamento)
{
return $metodoPagamento->getCommissione();
}
Siamo cosi "liberi" da if e switch infiniti, e l'aggiunta successiva di un nuovo metodo di pagamento non ci costringerà a cambiare una sola virgola di codice, solo ad aggiungere la nuova classe.
Chiaramente vale sempre la regola del buon senso. Qui si tratta sempre di oggetti, mentre costrutti come IF e SWITCH sono nativi del linguaggio ed hanno un impatto prestazionale quasi nullo. Ben vengano gli oggetti, ma se la situazione è quella di gestire un if su condizioni che, a priori, sappiamo che non cambieranno mai... teniamoci pure i nostri if.
SOLID
Mettendo assieme quanto appreso in queste prime tre lezioni, possiamo guardare al codice PHP con criteri diversi.
La stringa "SOLID", facilmente memorizzabile, ci ricorda dei punti chiave con cui valutare le nostre soluzioni.
S = Single Responsibility Principle. Una classe deve avere un solo motivo di cambiamento. Se ad esempio creiamo una classe "User" e dentro mettiamo il metodo che invia le email e quello che stampa l'ordine in pdf, la classe è esposta con il tempo a più modifiche. Potrebbe essere necessario cambiare il template html delle email, cosi come quello del pdf oppure la libreria usata per l'invio dei messaggi. Questo è un anti-pattern. E' importante quindi separare le varie funzionalità, cosi che esista una classe apposita per l'email ed un'altra per i pdf. La modifica dei formati delle email o dei pdf, interesserà solo la relativa classe.
O = Open/Closed Principle. Il codice deve essere estendibile senza modifiche: è qui che gli if/switch vengono meno.
L = Liskov Substitution Principle. Un oggetto deve poter essere sostituito da un suo derivato senza rompere il sistema.
I = Interface Segregation Principle. Meglio interfacce piccole e specifiche. Il che ricalca il principio visto alla lettera "S".
D = Dependency Inversion Principle. Dipendere da astrazioni, non da implementazioni concrete.
Quest'ultimo principio è spesso frainteso. Ecco un esempio pratico.
<?php
class PaymentService
{
private StripeGateway $stripe;
public function __construct()
{
$this->stripe = new StripeGateway();
}
}
Questo approccio è sbagliato visto che lega indissolubilmente Payment a Stripe, in una sortad di dipendenza hard-coded. La soluzione corretta è questa
<?php
interface PaymentGateway
{
public function pay(float $amount): void;
}
<?php
class StripeGateway implements PaymentGateway
{
public function pay(float $amount): void {}
}
<?php
class PaymentService
{
public function __construct(
private PaymentGateway $gateway
) {}
}
$service = new PaymentService(new StripeGateway());
A questo punto Payment potrà usare Stripe come qualsiasi altro gateway, compreso uno fittizio per i test. Si parla di "inversion" perchè da un approccio di questo tipo
<?php
business → dettagli (DB, mail, API)
si passa a
<?php
business → astrazione ← dettagli