Introduzione
Quando in PHP devi creare oggetti con molti parametri opzionali, configurazioni progressive o regole di costruzione non banali, il codice rischia di diventare confuso. Il classico costruttore con dieci argomenti è difficile da leggere, facile da sbagliare e complicato da mantenere.
In questi casi entra in gioco il Design Pattern Builder. L’idea è semplice: separare la costruzione di un oggetto dalla sua rappresentazione finale. In pratica, invece di passare tutto in un unico colpo al costruttore, costruisci l’oggetto passo dopo passo, con metodi dedicati e leggibili.
Questo pattern è molto utile in progetti reali, ad esempio per:
- creare query complesse;
- configurare oggetti di servizio con molte opzioni;
- generare documenti, email o report con componenti variabili;
- evitare costruttori troppo lunghi e fragili.
Nel tutorial vedremo un esempio pratico: un builder per creare email HTML con destinatario, oggetto, testo, allegati e priorità. È un caso molto realistico e perfetto per capire il valore del pattern.
Codice completo
<?php
declare(strict_types=1);
/**
* Oggetto finale da costruire.
* Rappresenta una email completa.
*/
class Email
{
public function __construct(
private string $to,
private string $subject,
private string $body,
private array $attachments = [],
private bool $isHighPriority = false
) {}
public function send(): void
{
echo "Invio email a: {$this->to}n";
echo "Oggetto: {$this->subject}n";
echo "Corpo: {$this->body}n";
if (!empty($this->attachments)) {
echo "Allegati: " . implode(´, ´, $this->attachments) . "n";
}
echo $this->isHighPriority ? "Priorità: altan" : "Priorità: normalen";
echo "Email inviata con successo.n";
}
}
/**
* Builder: costruisce l´oggetto Email passo dopo passo.
*/
class EmailBuilder
{
private ?string $to = null;
private ?string $subject = null;
private ?string $body = null;
private array $attachments = [];
private bool $isHighPriority = false;
public function setRecipient(string $to): self
{
$this->to = $to;
return $this;
}
public function setSubject(string $subject): self
{
$this->subject = $subject;
return $this;
}
public function setBody(string $body): self
{
$this->body = $body;
return $this;
}
public function addAttachment(string $filePath): self
{
$this->attachments[] = $filePath;
return $this;
}
public function markAsHighPriority(): self
{
$this->isHighPriority = true;
return $this;
}
/**
* Costruisce l´oggetto finale.
* Controlla che i campi obbligatori siano presenti.
*/
public function build(): Email
{
if ($this->to === null) {
throw new InvalidArgumentException(´Il destinatario è obbligatorio.´);
}
if ($this->subject === null) {
throw new InvalidArgumentException(´L´oggetto è obbligatorio.´);
}
if ($this->body === null) {
throw new InvalidArgumentException(´Il corpo dell´email è obbligatorio.´);
}
return new Email(
$this->to,
$this->subject,
$this->body,
$this->attachments,
$this->isHighPriority
);
}
/**
* Permette di riutilizzare lo stesso builder in modo pulito.
*/
public function reset(): self
{
$this->to = null;
$this->subject = null;
$this->body = null;
$this->attachments = [];
$this->isHighPriority = false;
return $this;
}
}
// ----------------------
// ESEMPIO DI UTILIZZO
// ----------------------
$builder = new EmailBuilder();
$email = $builder
->setRecipient(´[email protected]´)
->setSubject(´Conferma ordine´)
->setBody(´Grazie per il tuo acquisto. In allegato trovi la ricevuta.´)
->addAttachment(´/files/ricevuta.pdf´)
->markAsHighPriority()
->build();
$email->send();
// Riutilizzo del builder per una seconda email
$secondEmail = $builder->reset()
->setRecipient(´[email protected]´)
->setSubject(´Richiesta assistenza´)
->setBody(´Ho bisogno di aiuto con il mio account.´)
->build();
$secondEmail->send(); Spiegazione
Nel codice abbiamo due elementi principali:
- Email: è il prodotto finale, cioè l’oggetto che vogliamo ottenere.
- EmailBuilder: è il costruttore progressivo che raccoglie i dati e crea l’oggetto finale con
build().
1. Il prodotto finale
La classe Email contiene i dati già pronti per l’uso. Il suo costruttore riceve tutti i valori necessari, ma non è esposto direttamente all’utente del builder. Questo è importante perché centralizza la creazione e mantiene l’oggetto coerente.
2. Il builder
EmailBuilder espone metodi piccoli e leggibili:
setRecipient()setSubject()setBody()addAttachment()markAsHighPriority()
Ogni metodo modifica uno stato interno e restituisce self. Questo permette il method chaining, cioè la concatenazione delle chiamate in modo fluido:
$email = $builder
->setRecipient(´[email protected]´)
->setSubject(´Conferma ordine´)
->setBody(´Testo...´)
->build(); Il vantaggio è evidente: il codice è più leggibile rispetto a un costruttore con molti parametri, soprattutto quando alcuni sono opzionali.
3. Validazione centralizzata
Il metodo build() controlla che i campi obbligatori siano presenti. Questo è un punto fondamentale del pattern: il builder non si limita a “raccogliere dati”, ma garantisce anche che il prodotto finale sia valido.
Se manca il destinatario, l’oggetto non viene creato e viene lanciata un’eccezione. In questo modo eviti oggetti incompleti o bug difficili da tracciare.
4. Reset e riuso
Il metodo reset() serve a riutilizzare lo stesso builder per più oggetti. In alcuni contesti è comodo, ma attenzione: se il builder mantiene stato interno, devi essere sicuro di azzerarlo correttamente prima di una nuova costruzione.
Questa attenzione ai dettagli è importante perché il Builder, a differenza di pattern più semplici, lavora spesso con stato mutabile.
Best practice
- Usa il Builder quando il costruttore diventa troppo complesso: se hai molti parametri opzionali o combinazioni diverse, il pattern migliora subito la leggibilità.
- Valida nel metodo build(): i controlli finali vanno fatti lì, non sparsi nei singoli setter.
- Separa chiaramente builder e prodotto: il builder deve costruire, non contenere la logica di business principale.
- Preferisci metodi con nomi espliciti:
markAsHighPriority()è più chiaro disetPriority(true). - Evita builder troppo generici: se un builder deve creare troppe cose diverse, probabilmente sta facendo troppo.
- Usa tipi e strict types: in PHP moderno è una buona pratica per ridurre errori e rendere il codice più affidabile.
- Valuta alternative più semplici: se l’oggetto ha solo 2 o 3 parametri, il Builder potrebbe essere eccessivo.
Un errore comune è pensare che il Builder serva sempre. In realtà è utile soprattutto quando la costruzione è articolata. Per oggetti semplici, un costruttore ben progettato o named arguments in PHP 8+ possono essere più pratici.
Riepilogo
Il Design Pattern Builder è una soluzione elegante per costruire oggetti complessi in modo progressivo, leggibile e sicuro. In PHP è particolarmente utile quando hai:
- molti parametri opzionali;
- configurazioni multiple;
- validazioni da eseguire prima della creazione;
- oggetti difficili da istanziare con un semplice costruttore.
Nel nostro esempio, il builder per le email ha reso il codice più chiaro, ha centralizzato la validazione e ha migliorato la manutenzione. Questo è esattamente il tipo di vantaggio che un buon design pattern dovrebbe offrire: non aggiungere complessità, ma ridurla dove serve.
Se lavori su API, servizi di notifica, generazione documenti o oggetti di configurazione, il Builder può diventare uno strumento molto utile nel tuo toolkit PHP.
Approfondisci con risorse ufficiali
- PHP Manual — documentazione ufficiale del linguaggio: https://www.php.net/manual/it/
- PHP: Typed Properties — utile per progettare oggetti più sicuri: https://www.php.net/manual/it/language.oop5.properties.php
- PHP: Exceptions — per gestire correttamente gli errori nel build(): https://www.php.net/manual/it/language.exceptions.php
- Design Patterns di Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides — il testo classico per approfondire i pattern creazionali e strutturali.
