Builder Pattern: costruire query senza perdere la ragione — articolo

> Builder Pattern: costruire query senza perdere la ragione

Pillar article sul Builder Pattern nel query builder del framework: fluent interface, composizione incrementale e la separazione tra costruzione e rappresentazione.

Luigi Iadicola
~6 min lettura
#PHP 8.4 #ORM #Query Builder #Design Pattern
Builder Pattern: costruire query senza perdere la ragione
Builder Pattern: costruire query senza perdere la ragione

La stringa SQL e una trappola

Concatenare stringhe SQL a mano e il modo piu rapido per ottenere tre cose: un'SQL injection, un bug di sintassi e un'ora persa a debuggare una virgola mancante. Chi ha lavorato con PHP abbastanza a lungo ha visto query costruite con $sql .= " AND " . $column . " = '" . $value . "'" — codice fragile, illeggibile e pericoloso. Il Builder Pattern esiste per un motivo preciso: separare la costruzione di un oggetto complesso dalla sua rappresentazione finale.

Nel caso del query builder di Soft PHP MVC, l'oggetto complesso e la query, e la rappresentazione e la stringa SQL. Article::query()->where('is_active', 1)->orderBy('created_at', SortDirection::DESC)->limit(10)->get() non produce SQL fino alla chiamata finale get(). Ogni metodo aggiunge un pezzo alla struttura interna — clausole WHERE, ORDER BY, LIMIT — senza materializzare nulla. La query viene "compilata" solo quando serve, dalla grammar del driver corrente.

Cos'e il Builder Pattern: definizione formale

Il Gang of Four definisce il Builder come un pattern creazionale che "separa la costruzione di un oggetto complesso dalla sua rappresentazione, in modo che lo stesso processo di costruzione possa creare rappresentazioni diverse". I tre attori principali sono: il Builder (interfaccia per la costruzione), il ConcreteBuilder (implementazione che accumula le parti), e il Director (che orchestra il processo).

A differenza del Factory Method, che crea l'oggetto in un solo passo, il Builder lo costruisce incrementalmente. Ogni chiamata a un metodo del builder aggiunge un pezzo all'oggetto in costruzione. Solo alla fine — con un metodo come build() o get() — l'oggetto viene materializzato nella sua forma finale. Questa costruzione incrementale e cio che rende il pattern ideale per oggetti con molti parametri opzionali.

Fluent interface: il codice che si legge come una frase

Martin Fowler conio il termine fluent interface per descrivere API dove ogni metodo restituisce $this, permettendo chiamate a catena. Non e solo zucchero sintattico: una fluent interface comunica un ordine naturale di composizione. select()->from()->where()->orderBy()->limit() racconta una storia che un programmatore SQL riconosce immediatamente. La leggibilita non e un lusso — e documentazione vivente nel codice.

In Soft PHP MVC, il query builder supporta composizione condizionale con when(): ->when($onlyActive, fn($q) => $q->where('is_active', 1)). La query si costruisce solo se la condizione e vera — senza if esterni che rompono la catena. E il Builder che si adatta al contesto, non il contesto che si adatta al Builder. Questo approccio elimina l'annidamento di condizionali che renderebbe il codice illeggibile.

Metodi disponibili nel query builder

Il query builder del framework espone una superficie API ricca ma coerente:

  • select() — definisce le colonne da recuperare, con supporto per alias e raw expression
  • where() / orWhere() — clausole condizionali con operatori di confronto, LIKE, IN, BETWEEN
  • join() / leftJoin() — join tra tabelle con sintassi fluente
  • orderBy() — ordinamento con l'enum SortDirection di PHP 8.4
  • limit() / offset() — paginazione con parametri sicuri
  • when() — costruzione condizionale senza rompere la catena
  • groupBy() / having() — aggregazioni con filtri post-raggruppamento

Ogni metodo restituisce $this, mantenendo la fluent interface intatta. La consistenza dell'API e un obiettivo progettuale: non importa quanti metodi chiami nella catena, il tipo di ritorno e sempre il builder stesso — fino alla chiamata terminale.

Il direttore invisibile

Nel pattern classico del GoF, il Director orchestra la costruzione passo dopo passo. Nel query builder, il direttore e il codice applicativo — il controller, il service, il repository che chiama i metodi nella sequenza giusta. Ma c'e un direttore nascosto: la grammar, che sa come tradurre la struttura interna in SQL valido per il database specifico. La grammar e il traduttore finale, il componente che materializza l'astrazione in sintassi concreta.

Questa separazione tra struttura e rappresentazione e cio che rende possibile il supporto multi-database. La stessa catena di metodi produce SQL diverso su MySQL, PostgreSQL, SQLite e MariaDB. Il Builder non sa (e non deve sapere) quale database ricevera la query — sa solo come accumulare clausole. La traduzione e responsabilita della grammar, non del builder. Qui il Builder Pattern collabora con lo Strategy Pattern: la grammar e la strategia di compilazione.

Subquery e composizione ricorsiva

Un Builder puo contenere altri Builder. Le subquery — whereIn('category_id', fn($sub) => $sub->select('id')->from('categories')->where('active', 1)) — sono Builder annidati che vengono compilati ricorsivamente. E il momento in cui il pattern mostra la sua vera potenza: la composizione non ha limiti di profondita, ma ogni livello rimane leggibile e testabile indipendentemente.

Questa composizione ricorsiva e possibile perche il builder mantiene una rappresentazione ad albero della query, non una stringa. Ogni nodo dell'albero puo essere un valore semplice, un'espressione, o un altro builder. La compilazione attraversa l'albero in profondita, traducendo ogni nodo nella sintassi corretta. I parametri vengono raccolti in ordine di traversata, garantendo la corrispondenza tra placeholder ? e valori nel prepared statement.

Binding dei parametri e sicurezza

Il builder non inserisce mai valori direttamente nella stringa SQL. Ogni valore viene raccolto come binding e passato al prepared statement di PDO. Questo approccio previene le SQL injection per design: non e lo sviluppatore che deve ricordarsi di usare htmlspecialchars() o addslashes() — e l'architettura stessa che rende impossibile l'errore. La sicurezza non e un'opzione, e una conseguenza del pattern.

Quando usare il Builder Pattern

Il Builder e ideale quando l'oggetto da costruire ha molti parametri opzionali, quando la costruzione richiede piu passaggi, o quando lo stesso processo deve produrre rappresentazioni diverse. I segnali che indicano la necessita di un Builder includono:

  • Costruttori con piu di 4-5 parametri, molti dei quali opzionali
  • La necessita di validare l'oggetto solo dopo che tutti i pezzi sono stati assemblati
  • Lo stesso processo di costruzione che produce output diversi (SQL per MySQL vs PostgreSQL)
  • Oggetti immutabili che devono essere configurati prima della creazione

Christopher Alexander, l'architetto che ispiro i design pattern del software, parlava di "pattern language" — un vocabolario di soluzioni componibili. Il Builder e esattamente questo: un vocabolario per costruire query che si compone come un linguaggio naturale, dove ogni parola (metodo) ha un significato preciso e la grammatica (la fluent interface) guida la composizione corretta. Nel contesto di un framework PHP, e il pattern che trasforma la complessita delle query in codice espressivo e sicuro.

altri articoli