Enum e il potere di dare un nome alle cose — articolo

> Enum e il potere di dare un nome alle cose

Pillar article sugli enum backed PHP 8.4 nel framework: SortDirection, AggregateFunction, JoinType e la filosofia del type safety.

Luigi Iadicola
~5 min lettura
#PHP 8.4 #Type Safety #ORM #Filosofia
Enum e il potere di dare un nome alle cose
Enum e il potere di dare un nome alle cose

In principio era la stringa

C'e un momento, nello sviluppo di ogni codebase, in cui le stringhe magiche diventano insostenibili. orderBy('name', 'ASC') funziona — finche qualcuno scrive 'asc' in minuscolo, o 'ascending', o un typo come 'ACS'. Il compilatore non dice nulla, i test passano se non coprono quel caso, e il bug emerge in produzione come un risultato ordinato al contrario. Invisibile, silenzioso, irritante.

Wittgenstein scrisse che di cio di cui non si puo parlare si deve tacere. Nel codice, se un parametro puo assumere solo due valori — ASC e DESC — non dovrebbe essere possibile passare un terzo. Le stringhe non hanno questa proprieta. Gli enum si.

Il problema delle stringhe magiche va oltre il singolo typo. E un problema di comunicazione: quando leggi orderBy('name', 'ASC'), devi gia sapere che il secondo parametro accetta 'ASC' o 'DESC'. Non c'e nulla nel tipo che te lo dica. Con orderBy('name', SortDirection::ASC), l'informazione e nel codice stesso — l'IDE ti mostra i valori possibili, il type checker verifica la correttezza, il refactoring e sicuro.

Cinque enum, cinque domini chiusi

In Soft PHP MVC sono stati introdotti cinque enum in App\Core\DataLayer\Enum:

  • SortDirection (ASC, DESC) — usato in orderBy(). Due valori, nessuna ambiguita
  • AggregateFunction (COUNT, SUM, AVG, MAX, MIN) — usato in selectAggregate(). Cinque operazioni, tutte note a priori
  • JoinType (INNER, LEFT, RIGHT, CROSS) — usato internamente dal query builder. Ha permesso di estrarre un metodo buildJoin() unico, eliminando quattro implementazioni quasi identiche
  • ForeignKeyAction (CASCADE, SET_NULL, RESTRICT, NO_ACTION, SET_DEFAULT) — usato in foreignKey(), onDelete(), onUpdate(). Azioni referenziali del database, un dominio finito e ben definito
  • ColumnType (STRING, TEXT, INTEGER, BIGINT, BOOLEAN, JSON, ...) — i tipi colonna supportati dal sistema di migrazioni

Ogni enum e un dominio chiuso: un insieme finito di valori possibili. Non puoi creare un SortDirection che non sia ASC o DESC. Il tipo stesso impedisce l'errore, non il codice che lo usa.

L'impatto sul query builder

Prima degli enum, il query builder aveva quattro metodi quasi identici per i join: innerJoin(), leftJoin(), rightJoin(), crossJoin(). Ognuno conteneva la stessa logica con una stringa diversa. Con JoinType, un singolo metodo buildJoin(JoinType $type, ...) gestisce tutti i casi. Il codice duplicato e stato eliminato, e aggiungere un nuovo tipo di join (se SQL ne inventasse uno) richiederebbe solo un nuovo case nell'enum.

Questo e un esempio concreto di come il type safety non sia solo una questione di correttezza: e una questione di architettura. Un dominio ben tipizzato guida naturalmente verso codice piu compatto e piu espressivo.

Union type per la retrocompatibilita

I metodi accettano Enum|string come union type. Questo significa che il codice esistente — che passa stringhe — continua a funzionare. Ma il nuovo codice puo scegliere la strada tipizzata: orderBy('name', SortDirection::ASC). La migrazione e graduale, non forzata.

C'e una lezione di design qui: le transizioni migliori sono quelle che non costringono. Offrire la nuova strada senza chiudere la vecchia rispetta chi ha gia scritto codice funzionante. Col tempo, l'analisi statica (PHPStan, Rector) potra suggerire la migrazione — ma sara una scelta, non un obbligo.

Come funziona internamente la risoluzione del tipo

Quando il query builder riceve un parametro Enum|string, il flusso e semplice. Se il valore e un'istanza di BackedEnum, viene estratto il valore scalare con ->value. Se e una stringa, viene usato direttamente. Questo pattern di risoluzione e centralizzato in un metodo helper, cosi ogni punto del query builder gestisce i tipi in modo uniforme:

  • orderBy() accetta SortDirection|string per la direzione
  • selectAggregate() accetta AggregateFunction|string per la funzione
  • foreignKey() accetta ForeignKeyAction|string per onDelete e onUpdate

La coerenza di questo pattern e fondamentale: non importa in quale punto del query builder ti trovi, il comportamento e sempre lo stesso. Enum se vuoi type safety, stringa se preferisci la semplicita.

Nominare e comprendere

Confucio sosteneva che la rettificazione dei nomi fosse il primo passo verso l'ordine. Se i nomi sono sbagliati, il discorso non ha senso; se il discorso non ha senso, le cose non si compiono. Nel codice, dare un nome preciso a un concetto — JoinType::LEFT invece di 'LEFT' — non e pedanteria: e chiarezza. L'IDE ti suggerisce i valori possibili, il refactoring e sicuro, la documentazione e nel tipo stesso.

Gli enum in PHP 8.1+ sono backed — hanno un valore scalare (string o int) che si ottiene con ->value. Questo li rende perfetti per i confini del sistema: il query builder usa l'enum internamente, ma quando genera SQL produce la stringa ASC o LEFT JOIN. Il dominio dell'applicazione parla in tipi; il dominio del database parla in stringhe. L'enum traduce tra i due.

Enum e serializzazione: attraversare i confini

Un vantaggio spesso trascurato degli enum backed e la loro serializzabilita naturale. Un SortDirection::ASC si converte in "ASC" con ->value e si ricostruisce da stringa con SortDirection::from("ASC"). Questo li rende ideali per i confini del sistema dove i dati attraversano formati diversi: dal PHP al SQL, dal PHP al JSON per le API, dal form HTML al controller. L'enum garantisce che il valore sia valido in ogni passaggio — se la stringa non corrisponde a un case dell'enum, from() lancia un'eccezione immediata invece di propagare un valore invalido nel sistema.

Da stringa magica a tipo: un piccolo atto di civilta

Sostituire una stringa con un enum non cambia il comportamento del programma. I test passano prima e dopo. L'SQL generato e identico. Eppure qualcosa di importante e cambiato: l'intento e esplicito, l'errore e impossibile, il refactoring e sicuro. E un piccolo atto di civilta — rendere il codice un po' piu onesto su cio che si aspetta e un po' piu difficile da usare male.

Nel panorama dell'architettura software PHP, gli enum rappresentano un punto di svolta culturale. Per anni, la community PHP ha accettato le stringhe magiche come inevitabili. L'introduzione degli enum backed in PHP 8.1, e il loro utilizzo sistematico in framework come Soft PHP MVC, dimostra che il type safety non e un lusso riservato a linguaggi staticamente tipizzati: e una scelta di design accessibile anche in PHP, con benefici immediati sulla qualita e manutenibilita del codice.

altri articoli