CodeIgniter vs Laravel nel 2026: quando una PMI deve davvero migrare e come farlo senza fermarsi
In un audit su un gestionale di una PMI piemontese del settore manifatturiero, ho ereditato un applicativo CodeIgniter 3.1.10 in produzione da otto anni: PHP 7.4, libreria Email.php ancora vulnerabile al pattern di CVE-2016-10131 (CVSS 9.8 - Email header injection che porta a esecuzione di comandi sendmail), zero test automatici, 142 controller, 89 modelli, 11 librerie custom e tre integrazioni esterne con webservice di fornitori. L'azienda voleva "aggiungere il login Microsoft 365" per il portale fornitori. Il problema vero stava sotto: PHP 7.4 era fuori supporto da gennaio 2022, PHP 8.1 ha raggiunto EOL il 31 dicembre 2025 e CodeIgniter 3 riceve solo patch di sicurezza occasionali senza supporto garantito a PHP 8.2+. La domanda non era più "Laravel o CodeIgniter": era "quanto possiamo permetterci di restare fermi prima che il prossimo CVE non patchato ci costi un cliente o un'ispezione del Garante".
Perché CodeIgniter 3 è in fine corsa nel 2026 e CodeIgniter 4 non basta da solo?
CodeIgniter 3 è in modalità "maintenance-only" da diversi anni: il team aggiunge solo fix critici quando la community li segnala, niente nuove feature, nessun impegno formale a supportare le versioni PHP correnti. Non esiste una data ufficiale di end-of-life - il che è peggio di una data certa, perché lascia ogni PMI a decidere da sola quando staccare la spina. La lapide reale è arrivata da PHP: con PHP 8.1 fuori supporto dal 31 dicembre 2025, una qualunque vulnerabilità del runtime resta senza patch ufficiale. Le versioni PHP attualmente supportate sono 8.2 (security fix fino a dicembre 2026), 8.3 (fino a dicembre 2027) e 8.4 (fino a dicembre 2028).
CodeIgniter 4 è molto più moderno: namespace, dependency injection, factory pattern, gestione Composer-first, query builder maturo, supporto a PHP 8.x. Ma non è un free pass. A luglio 2025 è stata pubblicata CVE-2025-54418 - command injection critica (CVSS 9.8) nel handler ImageMagick di CI4: applicazioni che processano upload con resize() o text() accettando filename o testo controllati dall'utente possono essere compromesse fino alla 4.6.2. Lezione operativa: passare da CI3 a CI4 senza un security review serio significa scambiare una superficie d'attacco datata con una più moderna ma altrettanto affilata se non viene hardenizzata. Il JetBrains State of PHP 2024 misura 61% di sviluppatori Laravel contro 11% CodeIgniter - un divario che si traduce in tempi di patch più rapidi, qualità di documentazione superiore e disponibilità di sviluppatori sul mercato (parametro vitale per una PMI che dipende da freelance o consulenti esterni).
Architettura e developer experience: convenzione, dependency injection ed Eloquent
Il salto qualitativo di Laravel rispetto a CI3 si vede nella prima ora di lavoro. Il container di servizi, il routing dichiarativo e Eloquent come ORM trasformano operazioni che in CI3 richiedevano dieci righe in due. Su un'integrazione tipica fra ordine, cliente e righe di dettaglio, il codice CI3 di partenza è discorsivo:
// CodeIgniter 3 - application/models/Order_model.php
public function get_order_with_items($order_id)
{
$this->db->select('o.*, c.business_name, c.vat_number');
$this->db->from('orders o');
$this->db->join('customers c', 'o.customer_id = c.id', 'left');
$this->db->where('o.id', (int) $order_id);
$order = $this->db->get()->row_array();
if (! $order) {
return null;
}
$this->db->select('oi.*, p.sku, p.name');
$this->db->from('order_items oi');
$this->db->join('products p', 'oi.product_id = p.id', 'left');
$this->db->where('oi.order_id', $order['id']);
$order['items'] = $this->db->get()->result_array();
return $order;
}L'equivalente Laravel 12, una volta dichiarate le relazioni Eloquent, è dichiarativo, riusabile e - soprattutto - esplicito sul rischio N+1:
// Laravel 12 - app/Models/Order.php
class Order extends Model
{
public function customer()
{
return $this->belongsTo(Customer::class);
}
public function items()
{
return $this->hasMany(OrderItem::class);
}
}
// Service o controller
$order = Order::with(['customer', 'items.product'])->findOrFail($orderId);Eloquent forza l'eager loading via with(); l'approccio CI3 introduce silenziosamente N+1 non appena qualcuno scrive una foreach sui risultati. Il guadagno reale, però, non è la verbosità: è che Laravel ti dà strumenti operativi che in CI mancano o sono opzionali. Artisan, le migrations versionate, le code di lavoro con php artisan queue:work, lo scheduler integrato (schedule:run via cron), Laravel Octane per servire l'applicazione con Swoole o RoadRunner, Pest o PHPUnit con factory di test dichiarative. Il middleware avanzato di Laravel 12 permette di centralizzare autenticazione, rate limiting e logging in modo molto più pulito di quanto sia fattibile in CI3 senza incollare hook custom in ogni controller.
C'è poi il livello che CI3 non offre proprio: testabilità reale. Su un controller CI3 testare significa boot del framework, mock manuale di $this->db, fixture caricate via SQL grezzo. Su un service Laravel 12 il test è isolato, veloce e leggibile, perché il container risolve le dipendenze e le factory generano dati validi:
// tests/Feature/OrderServiceTest.php (Pest)
it('calculates totals including VAT', function () {
$customer = Customer::factory()->create(['vat_rate' => 22]);
$order = Order::factory()
->for($customer)
->has(OrderItem::factory()->count(3)->state(['unit_price' => 10, 'quantity' => 2]))
->create();
expect($order->fresh()->totalWithVat())->toBe(73.20);
});Senza una rete di test del genere, ogni rifattorizzazione su CI3 è un atto di fede; con questa rete, una migrazione modulo per modulo diventa un esercizio di sostituzione controllata.
Quanto costa davvero migrare un applicativo CodeIgniter a Laravel 12 e come farlo senza fermarsi?
La risposta secca: meno di quanto sembri se si applica lo strangler pattern, descritto da Martin Fowler nel 2004 e ancora oggi lo standard de facto per migrazioni di sistemi legacy. Il principio è semplice: non riscrivere il monolite in un colpo solo, ma far convivere vecchio e nuovo dietro un proxy comune, spostando un modulo alla volta finché il vecchio non scompare per "soffocamento" naturale.
Sul progetto piemontese ho applicato questa procedura in cinque fasi:
- Mappatura delle dipendenze. Raggruppare i 142 controller CI3 in moduli funzionali (anagrafica clienti, ordini, magazzino, fatturazione, portale fornitori). Misurare reads/writes su ciascuna tabella per priorizzare i moduli ad alto volume e quelli meno accoppiati.
- Bootstrap Laravel sullo stesso DB. Creare un'applicazione Laravel 12 che monta lo schema esistente via migrations "marker" senza ricreare le tabelle. Eloquent funziona con qualunque schema legacy purché si dichiarino correttamente
protected $tableeprotected $primaryKey. - Reverse proxy davanti a entrambi. Nginx con
try_filesche instrada/v2/*verso Laravel e tutto il resto verso CI3. Sessioni condivise via Redis o tabella DB (SESSION_DRIVER=databasesu Laravel +sess_save_path = databasesu CI3 puntano alla stessa tabella). - Migrazione modulo per modulo. Si parte dal modulo meno accoppiato - nel mio caso, il portale fornitori, completamente isolato dal gestionale principale. Ogni modulo migrato libera codice CI3 e lo rimpiazza con codice Laravel testabile.
- Cutover finale. Quando il modulo "core" (anagrafica + ordini) è migrato, si elimina CI3 e si aggiorna il proxy.
Esempio di route Laravel che incapsula una chiamata a un endpoint CI3 finché il modulo "magazzino" non è ancora stato riscritto:
// routes/web.php
use Illuminate\Support\Facades\Http;
Route::get('/v2/magazzino/{sku}', function (string $sku) {
// Bridge temporaneo: chiama l'endpoint CI3 esistente con HTTP interno
$response = Http::withHeaders([
'X-Internal-Bridge' => config('app.bridge_secret'),
])->timeout(5)->get(config('app.legacy_url') . '/magazzino/scheda/' . urlencode($sku));
abort_if($response->failed(), 502, 'Backend legacy non raggiungibile');
return view('v2.magazzino.scheda', [
'item' => $response->json(),
]);
})->middleware(['auth', 'throttle:60,1']);Quando il modulo magazzino viene riscritto in Laravel, si elimina la closure e si sostituisce con un controller dedicato. Nessun "big bang" di migrazione, nessun freeze prolungato delle feature, nessuna riscrittura totale che rischia di fallire al cutover. Per il lato dati, l'approccio è coerente con il refactoring di gestionali PHP legacy con test automatici: ogni modulo Laravel arriva con test PHPUnit/Pest che bloccano regressioni rispetto al comportamento CI3 originale, usando snapshot test sui payload JSON e fixture sui dati di anagrafica.
Sicurezza, ciclo di patch e disponibilità di sviluppatori
La differenza più tangibile fra Laravel e CodeIgniter 3 è il ciclo di patch. Laravel garantisce due anni di security fix per ogni major release: Laravel 12 (rilasciato il 24 febbraio 2025) riceve security patch fino a febbraio 2027; Laravel 13, rilasciato il 17 marzo 2026, estende ulteriormente la finestra. CodeIgniter 3 non ha una policy equivalente - solo patch sporadiche di sicurezza, comunicate via GitHub e mailing list, senza SLA. Su un'infrastruttura che tratta dati personali, dove le PMI italiane sono soggette al Regolamento UE 2016/679 (GDPR) e (per molti settori) alla Direttiva NIS2, un framework senza patch program documentato è un rischio compliance, non solo tecnico. La checklist di hardening per applicazioni Laravel/Symfony che applico ai miei progetti dipende dall'esistenza di patch tempestive - premessa che CI3 non garantisce.
Sul fronte delle competenze, il già citato JetBrains State of PHP 2024 e lo Stack Overflow Developer Survey 2024 mostrano che Laravel è di gran lunga il framework PHP più adottato (61% degli sviluppatori PHP), mentre CodeIgniter resta intorno all'11%, in calo costante. Per una PMI questo significa tempi di assunzione più lunghi, costi più alti e difficoltà nel trovare un consulente disposto a prendere in carico CI3 senza richiedere una migrazione come pre-condizione. È lo stesso problema che osservo sui progetti dove cerco un programmatore PHP senior freelance per affiancarmi: chi vuole lavorare a tempo pieno su CI3 nel 2026 è una nicchia ristretta e in via di esaurimento.
Quando NON ha senso migrare a Laravel e cosa fare in alternativa
Non è sempre la risposta giusta. Negli ultimi dodici mesi ho gestito tre progetti dove ho consigliato di non migrare a Laravel (in due casi di restare su CodeIgniter aggiornato, in uno di non migrare per niente):
- Applicazione interna a vita breve. Tool di gestione magazzino destinato a essere sostituito entro 18 mesi da un ERP commerciale. La migrazione costerebbe più del valore residuo dell'applicativo. Soluzione: lift a CodeIgniter 4.6.x con PHP 8.3, hardening al perimetro (WAF, rate limit applicativo, accesso solo via VPN), congelamento delle feature, scadenza tracciata.
- Monolite molto custom con poca logica. Sull'80% del codice CI3 c'erano CRUD su tabelle anagrafiche, accessibile solo da rete interna, senza upload utente né integrazioni esterne. Migrare significava riscrivere senza guadagno funzionale. Soluzione: lift a PHP 8.2 supportato + minor patches manuali su
system/libraries/Email.phpesystem/core/Security.php, con audit annuale. - Applicativo abbandonato. Sistema di prenotazione interna ormai in disuso (4 utenti attivi/mese, valore residuo nullo). Soluzione: decommissionamento, archivio sola lettura su S3, redirect HTTP 410 sul vecchio endpoint.
La regola operativa che applico: la migrazione si giustifica quando il costo di mantenere CI3 supera il costo di migrare nei prossimi 24-36 mesi. Il calcolo include patch security manuali, ore di sviluppo perse a forzare PHP 8.x su un framework che non lo supporta ufficialmente, costi opportunità di feature non sviluppate per mancanza di librerie moderne (pensa solo a tutta la galassia Composer post-2018), e - dato spesso sottovalutato in fase di preventivo - il rischio reputazionale di un incident pubblico su una codebase ormai abbandonata dal vendor. Il costo nascosto del vecchio codice PHP è raramente nei file: è nelle ore di lavoro che nessuno ti fattura ma che pagano comunque.
La migrazione da CodeIgniter a Laravel non è un esercizio di tifo da framework, è una decisione di rischio: rischio di patch mancanti, rischio di compliance, rischio di non trovare chi tocca il codice. Per conoscere il mio approccio basato su strangler pattern, test di regressione automatici e cutover incrementali - quello che ha permesso alla PMI piemontese di andare in produzione su Laravel 12 senza fermare un giorno il gestionale legacy - visita la mia pagina professionale. Se la tua PMI ha un applicativo CodeIgniter 3 in produzione e non sei sicuro su quale strada prendere, contattami per una consulenza e partiamo da un audit del tuo codebase: in due settimane ti consegno una mappa dei moduli, una stima realistica del costo di migrazione e - se ha senso - un piano di transizione a Laravel 12 senza big-bang.