Refactoring del codice PHP legacy: guida pratica per modernizzare un'applicazione senza riscriverla
Il refactoring più costoso della mia carriera non è stato il più complesso tecnicamente - è stato quello su un gestionale PHP 5.6 di una PMI emiliana che gestiva ordini, magazzino e contabilità per un'azienda di 25 dipendenti. Il gestionale funzionava, il business dipendeva da quello, e ogni nuova funzionalità richiesta dal titolare - una dashboard, un report, un'integrazione con il corriere - costava tre volte quello che avrebbe dovuto costare, perché lo sviluppatore che la implementava passava il 70% del tempo a capire il codice esistente e il 30% a scrivere la modifica, pregando di non rompere qualcos'altro. Il debito tecnico aveva raggiunto il punto in cui il costo di manutenzione superava il costo di sviluppo.
Il titolare aveva chiesto un preventivo per riscrivere tutto da zero in Laravel. Due agenzie avevano quotato tra 80.000 e 120.000 euro, con tempi di 8-12 mesi durante i quali avrebbe dovuto mantenere operativi entrambi i sistemi. La mia proposta era diversa: refactoring incrementale. Non riscrivere, ma modernizzare pezzo per pezzo, senza mai fermare la produzione. Costo: un terzo del rewrite completo. Tempo: tre mesi per raggiungere un livello di manutenibilità dove ogni nuova funzionalità costa quello che dovrebbe costare. In questo articolo ti racconto come.
Stai cercando un Consulente Informatico esperto per modernizzare il tuo applicativo PHP legacy? Nel mio profilo professionale trovi l'esperienza concreta su refactoring incrementale, Strangler Fig Pattern e migrazione da PHP 5.x a 8.x. Contattami per una consulenza diretta.
Perché il "big rewrite" fallisce e il refactoring incrementale funziona?
Joel Spolsky lo scrisse nel 2000 e resta vero nel 2025: la riscrittura completa è la cosa peggiore che un'azienda software possa fare. La ragione è che il codice legacy, per quanto brutto, contiene anni di decisioni di business, bug fix, edge case e comportamenti che nessuna specifica documenterebbe mai. Quando riscrivi da zero, perdi tutta quella conoscenza codificata. Il risultato è un'applicazione nuova e pulita che però non gestisce i 200 edge case che il vecchio sistema gestiva - e li scopri uno alla volta in produzione, sotto pressione, mentre il business si ferma.
Il refactoring incrementale - che Martin Fowler definisce nel suo libro Refactoring come "cambiare la struttura interna del codice senza modificarne il comportamento osservabile" - evita questo rischio perché lavora sul codice esistente. Ogni modifica è piccola, testata e reversibile. Il sistema continua a funzionare durante tutto il processo. E la conoscenza di business codificata nel vecchio codice viene preservata.
La tecnica: Strangler Fig Pattern su PHP legacy
Lo Strangler Fig Pattern, documentato da Martin Fowler, è la strategia di refactoring che uso per applicazioni PHP legacy monolitiche. Il nome viene dalla pianta tropicale strangler fig che cresce intorno a un albero esistente, eventualmente sostituendolo - ma l'albero originale continua a vivere durante l'intero processo.
In pratica, significa costruire le nuove funzionalità (o le riscritture di quelle vecchie) in un layer moderno che coesiste con il codice legacy. Un reverse proxy (Nginx) instrada le richieste: le URL nuove vanno all'applicazione moderna, quelle vecchie vanno ancora al codice legacy. Man mano che le funzionalità vengono riscritte, il proxy instrada sempre più traffico verso il codice moderno, fino a che il codice legacy non serve più.
# Nginx: Strangler Fig routing
# Le nuove rotte vanno all'applicazione Laravel
location /api/v2/ {
proxy_pass http://127.0.0.1:8080; # Laravel su porta 8080
}
location /dashboard/ {
proxy_pass http://127.0.0.1:8080; # Dashboard riscritta in Laravel
}
# Tutto il resto va ancora al codice legacy
location / {
root /var/www/legacy;
index index.php;
try_files $uri $uri/ /index.php?$query_string;
}Sul gestionale emiliano, il primo modulo migrato è stato il report vendite - un'area critica per il business ma con poche dipendenze dal resto del sistema. In due settimane è stato riscritto in Laravel con Eloquent, test automatici e una UI moderna. L'URL è cambiata da /report_vendite.php a /dashboard/report/vendite e Nginx instradava la nuova URL a Laravel. Tutto il resto del gestionale continuava a funzionare esattamente come prima.
Il prerequisito: characterization test prima di toccare il codice
Non inizio mai un refactoring senza una suite di characterization test che catturi il comportamento attuale del sistema. Questi test sono la mia garanzia che ogni modifica al codice non cambia il comportamento osservabile - se un test fallisce dopo una modifica, so che ho rotto qualcosa prima che arrivi in produzione.
Sul gestionale emiliano, i primi 40 characterization test coprivano: login/logout, ricerca prodotti, inserimento ordine, calcolo totali, generazione fattura, export CSV. Quaranta test scritti in tre giorni che nei mesi successivi hanno catturato 23 regressioni - 23 volte in cui una mia modifica avrebbe rotto qualcosa se i test non l'avessero segnalata.
Le tecniche di refactoring che producono il maggiore impatto
Dependency Injection: disaccoppiare per rendere testabile
Il problema più comune nel codice PHP legacy è l'accoppiamento stretto: classi che creano le proprie dipendenze con new, funzioni che accedono a variabili globali, codice che chiama mysql_query direttamente. Ogni new nel corpo di un metodo è un punto di accoppiamento che rende il codice impossibile da testare in isolamento.
<?php
// PRIMA: accoppiamento stretto, impossibile da testare
class OrderProcessor
{
public function process(int $orderId): void
{
$db = new mysqli("localhost", "root", "password", "gestionale");
$order = $db->query("SELECT * FROM orders WHERE id = $orderId");
// ... logica di business mescolata con accesso al DB
}
}
// DOPO: Dependency Injection, testabile
class OrderProcessor
{
public function __construct(
private readonly PDO $db
) {}
public function process(int $orderId): array
{
$stmt = $this->db->prepare("SELECT * FROM orders WHERE id = :id");
$stmt->execute(['id' => $orderId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}La differenza è che la versione refactored riceve la connessione dall'esterno - nei test puoi passare un database di test, in produzione il database reale. Il codice non sa e non gli importa quale database sta usando. Questa trasformazione, ripetuta su ogni classe che accede al database, è il singolo refactoring con il maggiore impatto sulla manutenibilità.
PHPStan e Rector: automazione del refactoring
PHPStan con baseline per individuare i problemi, e Rector per le trasformazioni automatiche. La combinazione dei due strumenti ha modernizzato il codice emiliano da PHP 5.6 a PHP 7.4 in due giorni di lavoro - Rector ha applicato automaticamente le trasformazioni sintattiche (type hint, null coalescing, arrow functions) e PHPStan ha verificato che nessuna trasformazione avesse introdotto errori.
# 1. PHPStan per la baseline dei problemi esistenti
vendor/bin/phpstan analyse src/ --level 3 --generate-baseline
# 2. Rector per l'upgrade automatico PHP 5.6 → 7.4
vendor/bin/rector process src/ --dry-run # preview
vendor/bin/rector process src/ # applicazione
# 3. PHPStan per verificare che Rector non abbia rotto nulla
vendor/bin/phpstan analyse src/ --level 3Il risultato in tre mesi
Dopo tre mesi di refactoring incrementale, il gestionale emiliano era in una condizione radicalmente diversa:
- Moduli migrati a Laravel: report vendite, dashboard, API integrazione corriere - circa il 30% delle funzionalità
- Copertura test: 40 characterization test + 60 unit test sui moduli migrati
- PHPStan level: da 0 (inutilizzabile) a 5 (type safety buona)
- Tempo medio per nuova funzionalità: ridotto del 60% (da 5 giorni-uomo a 2)
- Bug in produzione: ridotti del 70% nel primo trimestre post-refactoring
Il costo totale dell'intervento è stato circa un terzo di quanto sarebbe costato il big rewrite - e il gestionale non ha mai smesso di funzionare durante il processo. Per l'articolo che copre il passo precedente - mettere il codice sotto version control - rimando alla guida sull'implementazione di Git su sistemi legacy, e per l'audit di sicurezza che spesso accompagna il refactoring.
Il refactoring incrementale non è la scelta più glamour - non puoi presentare al management un sistema "tutto nuovo". Ma è la scelta che funziona: costi controllati, rischio minimizzato, e un sistema che migliora ogni settimana senza mai fermare il business. Se il tuo applicativo PHP legacy è diventato un peso anziché un asset, e il preventivo per il big rewrite ti ha spaventato, c'è un'alternativa concreta. Contattami e definiamo un piano di refactoring incrementale per il tuo caso specifico.