
Una migrazione si elimina cancellandone il file, ma solo se non è mai stata eseguita. Controlla con php artisan migrate:status: se è Ran, lancia prima php artisan migrate:rollback --step=1, poi elimina il file. Per aggiungere una colonna a una tabella esistente non modificare mai la vecchia migrazione: creane una nuova con --table.
Le migrazioni versionano lo schema del database come Git versiona il codice: ogni modifica è un file, rigiocabile su qualsiasi ambiente. Tre operazioni coprono quasi tutto il lavoro quotidiano: aggiungere una colonna a una tabella che esiste già, annullare una migrazione ed eliminare un file che non si sarebbe dovuto creare.
Articolo del percorso Sviluppo web. Tutti i comandi e gli output qui sotto sono stati eseguiti su Laravel 13.30.1 con PHP 8.4.25.
Creare una migrazione
Il comando segue una convenzione di denominazione che determina il contenuto del file generato:
php artisan make:migration create_tasks_tableIl nome inizia con create, seguito dal nome della tabella al plurale, seguito da table. Laravel riconosce questo schema e precompila Schema::create() con id() e timestamps(), la colonna title è aggiunta qui per l’esempio, e i commenti generati sono stati tolti:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};Da Laravel 8 le migrazioni sono classi anonime restituite da return new class extends Migration. Le vecchie migrazioni con nome, del tipo class CreateTasksTable extends Migration, continuano a funzionare ma non vengono più generate. Allo stesso modo, $table->bigIncrements('id') ha lasciato il posto a $table->id(), e i metodi up() e down() dichiarano ora un tipo di ritorno void.
Se la tabella deve avere le date di creazione e di modifica, $table->timestamps() le aggiunge: la loro assenza provoca l’errore « Unknown column ‘updated_at’ ».
Eseguire la migrazione crea la tabella:
php artisan migrateAggiungere una colonna a una tabella esistente
Una volta pronto lo schema, le query Eloquent di tutti i giorni fanno il resto. Non modificare mai una migrazione già eseguita: i tuoi colleghi e i tuoi server l’hanno già giocata e non la rigiocheranno. Crea una nuova migrazione, indicando la tabella interessata con --table:
php artisan make:migration add_notes_to_tasks_table --table=tasksIl file generato contiene uno Schema::table() vuoto nei due sensi. Riempili: la colonna in up(), la sua rimozione in down().
public function up(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->text('notes')->nullable()->after('title');
});
}
public function down(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->dropColumn('notes');
});
}php artisan migrate 2026_09_02_194036_add_notes_to_tasks_table .................... 47.43ms DONEQui vale la pena ricordare due precauzioni.
Rendi la colonna nullable, oppure dalle un valore predefinito. Su una tabella che contiene già delle righe, una colonna NOT NULL senza valore predefinito fa fallire la migrazione: il database non sa che cosa scrivere nelle righe esistenti.
Scrivi sempre il down(). È lui a rendere reversibile la migrazione. Una migrazione senza down() trasforma il minimo passo indietro in un intervento manuale.
Posizionare una colonna con after() è proprio di MySQL e MariaDB. Su SQLite l’istruzione viene accettata senza errore ma ignorata: la colonna finisce in fondo alla tabella. Verificato su SQLite 3.46.1, dove notes si ritrova dopo updated_at nonostante l’after('title'). L’ordine delle colonne non ha alcuna conseguenza funzionale.
Annullare una migrazione già eseguita
Comincia col guardare a che punto sei. migrate:status elenca ogni migrazione con il suo lotto e il suo stato:
php artisan migrate:status 2026_09_02_100000_create_tasks_table ............................... [1] Ran
2026_09_02_194036_add_notes_to_tasks_table ......................... PendingUna migrazione Pending non è mai stata giocata: il suo file si può eliminare direttamente. Una migrazione Ran va prima annullata.
# annulla solo l'ultima migrazione
php artisan migrate:rollback --step=1
# annulla tutto l'ultimo lotto
php artisan migrate:rollbackIl rollback esegue il metodo down(). Nell’esempio precedente la colonna notes sparisce davvero dalla tabella e la migrazione torna in Pending. È in quel momento, e non prima, che puoi eliminare il file.
Esistono due comandi vicini, da riservare allo sviluppo:
# annulla tutto e rigioca tutto
php artisan migrate:refresh
# elimina tutte le tabelle, poi rigioca tutto
php artisan migrate:freshmigrate:fresh elimina le tabelle, comprese quelle che nessuna migrazione gestisce. Su un server di produzione il comando distrugge il database senza più alcuna conferma possibile una volta lanciato. Riservalo rigorosamente allo sviluppo locale.
Eliminare una migrazione
Non esiste un make:migration --delete: una migrazione si elimina cancellandone il file, in database/migrations, la cartella restituita da database_path() tra i percorsi dell’applicazione. L’unica domanda è se sia già stata eseguita.
Non è mai stata eseguita (stato Pending): elimina il file, nient’altro.
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.phpÈ già stata eseguita (stato Ran): annullala prima, poi elimina il file.
php artisan migrate:rollback --step=1
rm database/migrations/2026_09_02_194036_add_notes_to_tasks_table.phpEliminare il file non cancella la riga corrispondente nella tabella migrations dei database dove è già stata eseguita. Quegli ambienti conserveranno la colonna, senza più nessuna migrazione che la spieghi. In questo caso non eliminare niente: scrivi una nuova migrazione che disfi la modifica. Il principio è lo stesso di Git, dove a riscrivere una storia già pubblicata si preferisce un commit di annullamento.
Modificare una colonna esistente
Per cambiare un tipo, una lunghezza o la nullabilità, ridichiara la colonna e aggiungi change():
public function up(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->string('title', 500)->change();
});
}Buona notizia per chi arriva da una versione vecchia: il pacchetto doctrine/dbal, a lungo obbligatorio per questa operazione, non lo è più da Laravel 11. Verificato su Laravel 13.30.1, dove change() funziona senza dipendenze aggiuntive.
Gli attributi che non ripeti vanno persi. Una colonna ->nullable()->default('x') modificata con un semplice ->string('title', 500)->change() torna non nullable e senza valore predefinito. Ridichiara ogni volta la definizione completa.
Errori frequenti
nullable() o default().

