Il Design Pattern Builder in PHP: costruire oggetti complessi in modo ordinato

by theArchitect
SHARE
Il Design Pattern Builder in PHP: costruire oggetti complessi in modo ordinato
© Guida-HTML5.it

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 di setPriority(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

SHARE