Do one thing and do it well
Nel 1978 Doug McIlroy sintetizzo la filosofia Unix in tre righe. La prima — fai una cosa sola e falla bene — e forse il principio di design piu citato e meno applicato nella storia del software. E facile da capire, difficile da rispettare: la tentazione di aggiungere "un'altra piccola cosa" a un componente e costante, e prima che te ne accorga hai un oggetto che fa tutto e non fa niente bene.
Quando ho riscritto la CLI di Soft PHP MVC, questa tensione era ovunque. I vecchi comandi avevano cinque implementazioni diverse per il parsing degli argomenti. Ogni comando leggeva le opzioni a modo suo, gestiva gli errori a modo suo, produceva output a modo suo. Funzionava, ma ogni nuovo comando richiedeva di reinventare le stesse soluzioni — o peggio, di copiare codice da un comando esistente sperando che reggesse.
Il problema non era solo tecnico. Era concettuale: mancava un linguaggio condiviso tra i comandi. Senza una base comune, ogni sviluppatore (anche quando lo sviluppatore sei solo tu, sei mesi dopo) deve decifrare le convenzioni locali di quel singolo comando prima di poterlo modificare. La filosofia Unix non e solo efficienza — e comunicazione.
#[CliCommand]: dare un nome alle cose
Ludwig Wittgenstein scrisse che i limiti del linguaggio sono i limiti del mondo. Nel codice, dare un nome esplicito a qualcosa — un attributo, una classe, un metodo — non e solo una questione estetica: e un atto di comprensione. #[CliCommand('migrate:fresh', 'Drop all tables and re-run migrations', group: 'database')] non e solo metadata: e una dichiarazione di intenti. Il comando sa chi e, cosa fa, e dove appartiene.
L'uso degli attributi PHP 8.4 per dichiarare i comandi CLI rappresenta una scelta architetturale precisa. A differenza di un file di configurazione YAML o di un array centralizzato, l'attributo vive accanto al codice che descrive. Non c'e disallineamento possibile tra la dichiarazione e l'implementazione: se sposti il file, la dichiarazione si sposta con lui. Se rinomini la classe, il comando resta definito nel posto giusto.
L'auto-discovery via CommandDiscovery e il corollario naturale: se ogni comando si dichiara esplicitamente tramite attributi, il framework non ha bisogno di una registrazione manuale. Scansiona, riflette, raccoglie. Il Kernel non mantiene piu una lista hardcoded di 25 comandi — la genera da cio che trova. Meno codice da mantenere, meno possibilita di dimenticare una registrazione.
Il ruolo della Reflection API
Sotto il cofano, CommandDiscovery usa la Reflection API di PHP per leggere gli attributi a runtime. Questo e un esempio di metaprogrammazione pragmatica: il codice ispeziona se stesso per costruire la mappa dei comandi disponibili. Non serve un compilatore separato, non serve un passo di build — il framework si auto-configura ogni volta che parte, leggendo la struttura delle proprie classi.
Input e Output: i confini del comando
La classe Input unifica cinque pattern di parsing in uno solo: argomenti posizionali, flag booleani, coppie chiave-valore, flag corti combinati (-mcs), lookahead. La classe Output offre metodi tipizzati — info(), success(), error(), table() — senza chiamare mai exit() direttamente. Un comando che non chiama exit() e un comando testabile.
Questa separazione ricorda la distinzione kantiana tra fenomeno e noumeno, trasposta nel software: il comando non conosce il "mondo esterno" (lo stdin, lo stdout, il terminale). Conosce solo le astrazioni che Input e Output gli presentano. Potrebbe girare in un terminale, in un test, in un pipe — non gli interessa e non dovrebbe interessargli.
Perche i metodi tipizzati contano
La differenza tra echo "Migrazione completata" e $output->success('Migrazione completata') sembra cosmetica, ma ha conseguenze profonde. Il metodo tipizzato codifica l'intenzione semantica del messaggio: success indica un risultato positivo, error un fallimento, info un dato neutro. Questa semantica puo essere usata in molti modi: colorare l'output nel terminale, filtrare i log in produzione, formattare i risultati in JSON per un consumatore automatico. Con echo, il messaggio e solo una stringa — il significato si perde.
Command base class: il contratto implicito
La classe astratta Command stabilisce un lifecycle chiaro: handle(Input, Output): int. Il codice di ritorno e un intero — 0 per successo, qualsiasi altro valore per errore. E il contratto piu semplice possibile, e proprio per questo e il piu robusto. Non c'e ambiguita su cosa significhi "il comando e riuscito": restituisce zero.
Il metodo factory make() garantisce che ogni comando venga istanziato allo stesso modo. L'help auto-generato legge gli attributi e le definizioni di argomenti e opzioni, eliminando la necessita di scrivere manualmente la documentazione di ogni comando. Se il codice e la documentazione divergono, la documentazione mente — generarla dal codice elimina questa classe di bug.
L'help auto-generato come documentazione vivente
Quando esegui php soft help migrate:fresh, il framework non legge un file markdown o una stringa hardcoded. Legge gli attributi #[CliCommand], gli argomenti definiti via addArgument() e le opzioni definite via addOption(), e compone l'help screen in tempo reale. Questa e documentazione vivente: non puo divergere dal codice perche e il codice. Un principio che Martin Fowler chiamerebbe "executable specification" — la specifica che si esegue da sola.
26 comandi, un solo pattern
Migrare 26 comandi alla nuova architettura non e stato un esercizio di refactoring cosmetico. E stato un esercizio di disciplina: trovare cio che era comune, estrarlo, e lasciare a ogni comando solo cio che lo rende unico. make:model genera file da stub. migrate:fresh ricrea il database. key:generate produce una chiave crittografica. Tre comandi completamente diversi che condividono la stessa infrastruttura senza che questa li vincoli.
Elenco dei gruppi di comandi in Soft PHP MVC
- database — migrate, migrate:fresh, migrate:rollback, seed: gestione completa del ciclo di vita del database
- make — make:model, make:controller, make:middleware, make:command: generazione di codice da stub con placeholder
- cache — cache:clear, view:clear: gestione della cache applicativa e delle view compilate
- security — key:generate, csrf:rotate: operazioni crittografiche e di sicurezza
- maintenance — up, down, serve: gestione dello stato dell'applicazione
Ogni gruppo condivide convenzioni di naming (verbo:risorsa), stile di output, gestione degli errori. Un nuovo sviluppatore che ha visto un comando sa come funzionano tutti gli altri. Questa prevedibilita e il vero valore della filosofia Unix applicata al codice PHP.
La filosofia Unix non e solo un principio tecnico. E un'etica del design: rispetta chi verra dopo di te. Un comando che fa una cosa sola, con un'interfaccia chiara e un output prevedibile, e un comando che qualcuno potra usare, estendere e debuggare senza dover leggere tutto il codice del framework. In un'epoca in cui la complessita cresce esponenzialmente, la semplicita e un atto di resistenza — e forse l'unico che ha davvero impatto duraturo.