Perche tutti odiano il Singleton
Nessun design pattern ha una reputazione peggiore del Singleton. "Stato globale mascherato", "impossibile da testare", "accoppiamento nascosto" — le critiche sono note e spesso legittime. Un Singleton usato male e una variabile globale con un cappello elegante: nasconde le dipendenze, rende il codice imprevedibile e trasforma i test in un incubo di stato condiviso. In molti codebase PHP, il Singleton e il primo pattern che i developer imparano — e il primo che usano in modo sbagliato.
Eppure il Singleton esiste per un motivo. Ci sono risorse che per loro natura devono essere uniche: una connessione al database, una chiave di crittografia in memoria, la configurazione dell'applicazione. Avere due istanze di Database che aprono due connessioni diverse e un bug, non una feature. Il Singleton non e il problema — il problema e usarlo dove non serve, come scorciatoia per evitare il dependency injection.
Cos'e il Singleton Pattern: definizione e meccanica
Il Gang of Four descrive il Singleton come un pattern che "assicura che una classe abbia una sola istanza e fornisce un punto di accesso globale ad essa". L'implementazione classica prevede tre elementi: un costruttore privato (impedisce l'istanziazione diretta), una proprieta statica (conserva l'unica istanza), e un metodo statico (restituisce l'istanza, creandola al primo accesso).
In PHP, la protezione completa richiede anche di impedire il cloning e la deserializzazione. Senza queste precauzioni, il Singleton puo essere aggirato con clone $instance o unserialize(serialize($instance)). Il pattern diventa quindi una triplice difesa: costruttore privato, __clone() privato, e __wakeup() che lancia un'eccezione. E una quantita di codice difensivo che segnala quanto il pattern sia fragile se non implementato con rigore.
Database::getInstance() — il caso legittimo
In Soft PHP MVC, Database::getInstance() garantisce una sola connessione PDO per l'intera richiesta. Il costruttore e privato, il clone e vietato, l'istanza e statica. E il Singleton nella sua forma piu pura — e funziona perche il dominio lo richiede: non vuoi che ogni model apra la propria connessione, e non vuoi passare un oggetto PDO attraverso venti livelli di chiamate. Una singola connessione per richiesta e la norma in quasi tutti i framework PHP.
Il pattern make() adottato nel framework attenua il problema della testabilita: Database::make() restituisce l'istanza singleton, ma il metodo factory permette di interporre logica — in futuro, potrebbe accettare una connessione di test o leggere configurazioni alternative. Il costruttore privato impedisce istanze spurie; il factory method apre una porta per il testing senza abbattere il muro del Singleton. E un compromesso pragmatico che bilancia rigore architetturale e necessita pratiche.
Connessioni multiple: quando il Singleton non basta
Esiste un caso limite: le connessioni multiple. Se l'applicazione deve connettersi a due database diversi (lettura/scrittura, o database principale/legacy), un singolo Singleton non e sufficiente. La soluzione e un Registry di Singleton: ogni connessione e identificata da un nome, e il registry mantiene un'istanza unica per ogni nome. In Soft PHP MVC, questo scenario non si presenta ancora, ma l'architettura con make() e pronta per evolversi in questa direzione senza breaking change.
EncryptionService: un segreto che non si duplica
EncryptionService::make() carica la chiave di crittografia una sola volta e la tiene in memoria per tutta la richiesta. Duplicare questa istanza significherebbe duplicare un segreto crittografico in memoria — un rischio di sicurezza inutile. Il Singleton qui non e una scelta di convenienza ma di igiene: meno copie di un segreto esistono, meno superficie d'attacco c'e. Ogni copia in memoria e una potenziale fuga se l'applicazione subisce un memory dump.
Il servizio di crittografia incapsula anche la rotazione delle chiavi: quando la chiave cambia, il Singleton puo gestire la transizione in modo trasparente, decifrando con la vecchia chiave e ricifrando con la nuova. Se ci fossero istanze multiple, ognuna con la propria copia della chiave, la rotazione diventerebbe un coordinamento complesso e error-prone. L'unicita dell'istanza semplifica un'operazione critica per la sicurezza.
Singleton e testabilita: il vero problema
Il problema principale del Singleton nei test e lo stato condiviso tra i test. Se il test A modifica lo stato del Singleton, il test B lo trova in uno stato imprevedibile. I test non sono piu isolati, e l'ordine di esecuzione influenza i risultati — un incubo per il CI/CD. Le soluzioni possibili includono:
- Metodo reset() — un metodo statico che annulla l'istanza, usato solo nei test
- Iniezione via factory — il pattern
make()che permette di sostituire l'istanza nei test - Interface extraction — estrarre un'interfaccia dal Singleton e iniettarla dove serve
- Service locator come intermediario — un container che gestisce il lifecycle e permette il mocking
In Soft PHP MVC, il pattern make() e la scelta adottata: non e purismo accademico, e la risposta pragmatica di chi ha dovuto scrivere test per codice che usa Singleton.
Quando il Singleton diventa un anti-pattern
Il Singleton degenera quando viene usato come scorciatoia per evitare il dependency injection. Se un service e Singleton solo perche "era comodo chiamarlo da ovunque", stai mascherando una dipendenza — e ogni dipendenza nascosta e debito tecnico. La regola e pragmatica: Singleton per risorse che devono essere uniche per natura (connessioni, chiavi, configurazione), dependency injection per tutto il resto.
Ecco i segnali che il Singleton e usato male:
- La classe e Singleton ma non gestisce risorse limitate (connessioni, file handle, chiavi)
- Il Singleton viene chiamato da decine di punti diversi per evitare di passare il parametro
- I test falliscono quando cambiano ordine di esecuzione
- Non riesci a spiegare perche l'istanza debba essere unica, solo che "e comodo"
Il framework Soft PHP MVC usa entrambi gli approcci: Singleton dove l'unicita e un requisito del dominio, make() factory dove serve flessibilita futura, iniezione esplicita dove la testabilita e prioritaria. Non e purismo — e pragmatismo informato. Conoscere il pattern significa sapere quando usarlo e, soprattutto, quando evitarlo.