Reverse engineering di Laravel 8 su PHP 7.4 EOL senza documentazione: come ho mappato in dodici giorni il gestionale interno di una catena di cliniche dentistiche veronesi con 23 studi e 180 utenti attivi

Reverse engineering di Laravel 8 su PHP 7.4 EOL senza documentazione: come ho mappato in dodici giorni il gestionale interno di una catena di cliniche dentistiche veronesi con 23 studi e 180 utenti attivi

Come si mappa in modo economico un'applicazione Laravel di cui nessuno sa più nulla?

Il 23 giugno 2025 ho iniziato a lavorare su un gestionale interno per una catena di ventitré cliniche odontoiatriche nel Veneto occidentale, con sede amministrativa a Verona, 180 dentisti utenti tra tutti gli studi, una media di 1.800 appuntamenti al giorno sincronizzati tra il gestionale e le agende dei singoli studi, e una base dati di circa 350.000 pazienti attivi. L'applicazione era un Laravel 8.83 - uscito nel novembre 2021, ricevuti gli ultimi bugfix a gennaio 2022 e gli ultimi security patch a gennaio 2023, quindi in stato EOL da oltre due anni e mezzo rispetto alla data dell'intervento - girante su PHP 7.4.33, anch'esso in EOL dal 28 novembre 2022 e quindi scoperto da quasi tre anni da patch ufficiali di sicurezza. L'infrastruttura era un bare metal OVH Advance-3 nel datacenter di Strasburgo: Ryzen 5 PRO 3600X, 64 GB RAM, 2x 1 TB NVMe in RAID 1 software, Debian 10 Buster (anch'essa EOL, pattern ricorrente).

Lo sviluppatore originario era un freelance italiano che aveva costruito il gestionale dal 2018 al 2022 come progetto principale, progressivamente delegandolo a un secondo collaboratore junior e poi uscendo definitivamente dal progetto a fine 2022 per un trasferimento all'estero. Il junior aveva mantenuto il sistema con interventi sporadici fino alla primavera 2024, poi aveva trovato un lavoro dipendente e aveva smesso di rispondere. Dal giugno 2024 il gestionale girava senza manutenzione, aggiornamenti o interventi di qualsiasi tipo. Il titolare della catena di cliniche, gestore attento ma non tecnico, si era reso conto a maggio 2025 che ogni piccolo problema riportato dai dentisti - un pulsante non reattivo, un campo che non salvava, una stampa PDF con caratteri strani - non aveva più nessuno a cui essere segnalato. Mi ha cercato tramite un collega comune con una richiesta precisa: "Non voglio un refactor né una riscrittura. Voglio sapere che cosa ho, che cosa rischia di rompersi domani, e cosa posso fare per sopravvivere i prossimi dodici mesi senza catastrofi."

Lo scenario di 183 è quindi completamente diverso dai tre rewrites precedenti della stessa serie. Non è un'emergenza con downtime in corso (180, 182) né un sistema con Horizon e scheduler fermi (181): è un'applicazione che gira stabile ma non ha nessuno che possa intervenire, con un titolare che - intelligentemente - vuole capire la situazione prima di prendere qualsiasi decisione. In questo contesto, il reverse engineering sistematico del codice è la soluzione giusta, perché serve a produrre documentazione viva, misurabile, che rimane nel repository del cliente come asset riutilizzabile. In questo articolo racconto il metodo che applico in questi casi, scandito in dodici giorni di lavoro effettivi, con gli strumenti concreti che rendono il lavoro economicamente sostenibile anche per una PMI di dimensioni medie.

Stai cercando un Consulente Laravel per mappare un'applicazione legacy abbandonata dal team originario? Nel mio profilo professionale trovi l'esperienza concreta in reverse engineering di codebase Laravel e Symfony, audit di sicurezza su PHP legacy e produzione di documentazione tecnica per PMI. Contattami per una consulenza diretta.

Giorni 1-2: inventario senza modifiche, zero tocchi al repository

La regola d'oro del reverse engineering su codice legacy è: nei primi due giorni non modifichi niente. Nessun composer update, nessun php artisan cache:clear, nessun esperimento. L'unica cosa che fai è leggere. Questo non è rigorismo accademico: è che ogni modifica che fai prima di avere il quadro completo rischia di rompere qualcosa che non sapevi esistesse, e di mascherare lo stato reale del sistema. In un Laravel 8 su PHP 7.4 EOL, un composer install affrettato può già rompere dipendenze transitive che nel frattempo sono state rimosse da Packagist.

Le prime quarantotto ore le ho passate a clonare il repository in locale su un ambiente Docker isolato (per non toccare neppure le macchine di produzione del cliente), a leggere il composer.json, il package.json, il .env.example, la cartella config/, e a stilare un inventario grezzo dei componenti. Il metodo è questo, rigoroso e meccanico:

cd /workspace/dentistico
cat composer.json | jq '.require, .require-dev'
cat package.json | jq '.dependencies, .devDependencies' 2>/dev/null
php artisan --version
php artisan about 2>/dev/null
grep -rn "env(" config/ | wc -l
find config -type f -name "*.php" -exec basename {} \;
php artisan route:list --verbose --except-vendor 2>/dev/null | wc -l
php artisan event:list 2>/dev/null
find app/Jobs -name "*.php" | wc -l
find app/Console/Commands -name "*.php" | wc -l
find database/migrations -name "*.php" | wc -l
find app/Models -name "*.php" | wc -l

Il risultato di quella prima giornata l'ho scritto in un documento markdown che ho chiamato INVENTORY.md, salvato direttamente nel repository in una nuova cartella docs/reverse/ che ho creato appositamente. L'inventario iniziale era asciutto e preciso: Laravel 8.83, PHP 7.4 dichiarato in composer.json come "php": "^7.4" (nessun supporto 8.x), 47 dipendenze dirette in require, 12 dipendenze dev, un package.json con Vue 2.6 come frontend (!), 184 route applicative (escluse le route vendor di package come Horizon e Telescope che non erano installati), 34 job, 18 comandi artisan custom, 89 migration database, 43 modelli Eloquent. Sui file di configurazione, cinque file molto corposi: config/services.php, config/auth.php, config/database.php, config/queue.php, e un config/clinic.php custom di 340 righe che conteneva tutte le costanti applicative - un pattern rivelatore che il dev originario stava cercando di centralizzare la configurazione senza usare del tutto il pattern .env.

Questo inventario apparentemente grezzo mi ha già dato tre segnali operativi importanti: primo, il package Vue 2.6 era EOL da fine 2023 e l'intero frontend andava probabilmente rifatto o migrato; secondo, zero presenza di Horizon o Telescope significava che il progetto non aveva mai avuto una cultura di osservabilità; terzo, 43 modelli con 89 migration è un rapporto sano, nessuna esplosione di tabelle non modellate (che è tipicamente un red flag di shadow work eseguito con query raw fuori da Eloquent).

Giorni 3-5: mappare le route, gli endpoint e il flusso di autenticazione

Il secondo blocco di lavoro è stato la mappatura sistematica delle 184 route applicative. La tentazione è trattarle come una lista piatta, ma in una Laravel reale le route sono organizzate per dominio funzionale, e rispettare questa struttura dà ritorno esponenziale nella fase di audit successiva. Il comando php artisan route:list --verbose è il punto di partenza, ma output e lettura manuale diventano insostenibili oltre le 50-60 route. Il metodo che uso è questo: estrarre le route in JSON con un filtro personalizzato, raggruppare per prefisso URI e per middleware, e produrre una tabella markdown che diventi il capitolo 1 della documentazione viva.

php artisan route:list --json > docs/reverse/routes.raw.json
php artisan route:list --columns=method,uri,name,action,middleware --except-vendor > docs/reverse/routes.txt
grep -rn "Route::" routes/ | wc -l
grep -rn "middleware(" routes/ | head -30
awk '/Route::(get|post|put|delete|patch)/' routes/web.php | wc -l
awk '/Route::(get|post|put|delete|patch)/' routes/api.php | wc -l

Dei 184 endpoint, 112 erano in routes/web.php (interfaccia web gestita da Blade + Vue per i dentisti), 68 in routes/api.php (API REST consumate dal frontend Vue e da un'app mobile Android rudimentale per il check-in dei pazienti), 4 in routes/console.php. Il middleware chain era relativamente semplice: auth, auth.api, role:admin, role:dentist, role:receptionist, con un paio di middleware custom (clinic.context che iniettava il contesto della clinica corrente, e audit.log che loggava ogni operazione scritta in un log dedicato per compliance sanitaria). Questo flusso di autenticazione era già documentabile in una diagramma sequenza di sette passi: login → controllo credenziali su users → lookup del ruolo → controllo delle cliniche associate all'utente → selezione della clinica corrente se multipla → emissione del token di sessione → redirect al dashboard ruolo-specifico.

La cosa che mi ha colpito positivamente è stata trovare in routes/web.php dei commenti inline scritti dal dev originario (pochi, ma presenti) che spiegavano il razionale di alcuni raggruppamenti complessi: "Queste route sono duplicate nella versione mobile perché l'app Android non supporta il binding implicit route model". Questo tipo di commento, una volta incrociato con la realtà del codice, è oro puro per il reverse engineering perché documenta decisioni di design che altrimenti sarebbero opache. Li ho tutti copiati in un file docs/reverse/design-decisions.md perché riflettessero scelte architetturali da conservare in futuro.

Giorni 6-8: static analysis con PHPStan, Psalm e Deptrac per misurare il debito tecnico

Con l'inventario delle route e del flusso di auth consolidato, sono passato all'analisi statica del codice, che è dove il reverse engineering diventa quantitativo. Gli strumenti che uso in questa fase sono PHPStan a livello 0 (il più permissivo, per avere una baseline), Psalm con --show-info=false per concentrarsi solo sugli errori veri, e Deptrac per misurare le dipendenze architetturali tra layer. Tutti e tre questi strumenti si integrano con PHP 7.4 nonostante siano progettati per versioni più moderne, ed è uno dei motivi per cui li preferisco ad alternative più specifiche di Laravel.

L'installazione è rapida ma va fatta con attenzione perché installarli dentro il composer.json del progetto cliente potrebbe rompere le dipendenze esistenti. La mia prassi è installarli globalmente nell'ambiente Docker di analisi via composer global require oppure direttamente scaricando i .phar ufficiali, mantenendo il composer.json del progetto intonso. Il primo run di PHPStan a livello 0 sul codebase ha rivelato 1.247 errori, un numero alto ma non catastrofico per un'applicazione di quella dimensione. La distribuzione degli errori era molto significativa: il 62% erano type mismatches banali (cast impliciti tra string e int, array mai inizializzati), il 21% erano metodi chiamati su oggetti potenzialmente null, il 12% erano property access su classi che non dichiarano quelle property (tipico Eloquent dynamic), il 5% erano errori reali di logica che valeva la pena approfondire. Ho tracciato questa distribuzione in un file docs/reverse/static-analysis-baseline.md come baseline misurabile.

Deptrac, configurato con un layer base (Controllers, Models, Services, Jobs, Notifications, Observers), ha rivelato un'architettura ragionevolmente pulita ma con qualche violazione importante: tre Controller parlavano direttamente con i Model saltando completamente il layer Service, undici Model contenevano logica di business che avrebbe dovuto stare nei Service, cinque Service istanziavano direttamente Job senza passare dal container IoC. Queste violazioni non sono bug ma debiti architetturali, e documentarle è fondamentale per qualsiasi piano di refactoring futuro, che ho iniziato a stendere nel capitolo 4 della documentazione viva come mappa del debito tecnico da gestire nei 90 giorni successivi seguendo lo stesso metodo che applico in tutti i subentri.

Giorni 9-10: schema database, foreign key, dati sensibili sanitari

Il reverse engineering del database è stato il blocco più delicato perché si trattava di dati sanitari: 350.000 pazienti con anagrafica completa, storico visite, immagini RX, consensi informati, piani di pagamento, fatture SSN. Questo tipo di dati richiede attenzione specifica non solo per GDPR ma anche per il rispetto del segreto professionale medico, e ogni operazione di lettura andava documentata per poterla giustificare in caso di audit futuro. Ho fatto un dump strutturale del database (senza dati, solo schema) con mysqldump --no-data --routines --triggers dentistico > docs/reverse/schema.sql, ho contato le tabelle (142, di cui 89 gestite da Eloquent e 53 pivot o di supporto), e ho mappato le foreign key relations con una query INFORMATION_SCHEMA:

SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_SCHEMA = 'dentistico'
AND REFERENCED_TABLE_NAME IS NOT NULL
ORDER BY TABLE_NAME, COLUMN_NAME;

Il risultato, esportato in CSV, mostrava 187 foreign key totali. Le relazioni centrali erano patients → clinics (n:1), appointments → patients (n:1), appointments → dentists (n:1), invoices → patients (n:1), treatments → appointments (1:n), con una catena di dipendenze ordinatissima attorno al modello centrale Patient. Ho prodotto un diagramma ER semplificato con dbdiagram.io (gratuito, genera markdown/image da una DSL minimale) e l'ho salvato in docs/reverse/er-diagram.png come riferimento per qualsiasi modifica futura al database.

La mappatura dei dati sensibili è stata il sotto-compito più importante di questi due giorni. Ho identificato le colonne contenenti Personally Identifiable Information (nome, cognome, codice fiscale, indirizzo, telefono, email, data di nascita), quelle contenenti dati sanitari (diagnosi, anamnesi, piani terapeutici, allegati radiografici), e quelle contenenti dati di pagamento (IBAN, CC, importi). Questa lista è andata in docs/reverse/data-classification.md come base per qualsiasi futuro audit di compliance. Ho anche verificato se l'applicazione usasse il cast encrypted di Eloquent su queste colonne (non lo faceva - altra voce di debito tecnico), se APP_KEY fosse settata e stabile (sì, nessun rischio di perdita di accesso), e se ci fossero log applicativi che contenessero PII in chiaro (sì, in almeno tre punti del codice - da segnalare al titolare come rischio GDPR immediato).

Giorni 11-12: consegna del documento vivente, raccomandazioni operative

Gli ultimi due giorni li ho dedicati a organizzare tutto il materiale prodotto in un documento finale strutturato, consegnabile al titolare in forma PDF più il markdown vivo nel repository. Il documento finale aveva otto sezioni: inventario tecnologico (versioni, dipendenze, stato EOL), mappa dei 184 endpoint con raggruppamento funzionale, flusso di autenticazione e autorizzazione, schema database con diagramma ER e data classification, risultati di static analysis con baseline misurabile, lista delle violazioni architetturali Deptrac, mappa dei dati sensibili e raccomandazioni GDPR, piano operativo dei prossimi dodici mesi. Questa ultima sezione era la più importante dal punto di vista del cliente, perché traduceva tutte le analisi tecniche in decisioni concrete.

Il piano operativo dei dodici mesi che ho raccomandato al titolare era diviso in tre finestre. Finestra 0-90 giorni: rimuovere PII dai log applicativi (bloccante per GDPR), aggiornare dipendenze Composer senza cambiare major version, installare monitoring di base (Uptime Kuma + logwatch email), backup automatici offsite su Hetzner Storage Box, documentazione delle procedure di deploy anche se manuali. Finestra 90-180 giorni: pianificazione upgrade PHP da 7.4 a 8.2 (con test di compatibilità dipendenze), pianificazione upgrade Laravel da 8 a 10 (bridging via 9 intermedio), refactoring delle violazioni architetturali più gravi identificate da Deptrac, sostituzione di Vue 2.6 con una versione moderna o mantenimento esplicito. Finestra 180-365 giorni: esecuzione degli upgrade, testing strutturato delle funzionalità critiche via un minimo di test automatici sulla codebase Laravel legacy, implementazione di CI/CD minimale con GitHub Actions per mettere in sicurezza il ciclo di deploy.

La cosa interessante - e che racconto sempre ai clienti in questi scenari - è che un reverse engineering fatto in dodici giorni ha un valore che va ben oltre il documento stesso. La documentazione viva nel repository diventa la base su cui qualsiasi futuro sviluppatore potrà orientarsi senza dover ripartire da zero, riducendo drasticamente il costo di onboarding di un nuovo manutentore e il rischio di dipendenza esclusiva da un singolo tecnico. È il tipo di investimento che si ripaga al primo cambio di manutentore. Per un approccio parallelo focalizzato più sulla sicurezza applicativa del codice PHP legacy, vedi il mio approfondimento specifico; mentre per il tema del recupero del controllo di una codebase PHP legacy senza documentazione PMI ho scritto una guida complementare più orientata alla dimensione organizzativa che tecnica.

Se gestisci un'applicazione Laravel in produzione che nessuno in azienda sa più come intervenire, con documentazione assente e sviluppatori irreperibili, e vuoi capire esattamente cosa hai, quali rischi stai correndo e quale piano sia realistico per i prossimi dodici mesi, contattami direttamente. Posso eseguire un reverse engineering sistematico del codebase in tempi rapidi, produrre documentazione viva che rimane nel tuo repository come asset permanente, e consegnarti un piano operativo misurabile. Nel mio profilo professionale trovi l'esperienza concreta su Laravel legacy, audit di sicurezza su PHP EOL e modernizzazione graduale di applicazioni critiche per PMI italiane.

Ultima modifica: