Il sistema di migrazione di Laravel è progettato per semplificare la creazione e la condivisione dello schema collegato all’applicazione.
Personalmente ritengo questa feature una delle caratteristiche più importanti del framework. Ma al di là delle mie considerazioni personali, vediamo come funzionano le migrations di Laravel.
Le migrazioni sono classi che utilizzano il costruttore Schema di Laravel per definire facilmente lo schema del database. Queste classi sono contenute nella cartella database/migrations e possono essere generate attraverso la riga di comando
php artisan make:migration create_tablename_table
Artisan creerà un file all'interno della cartella migrations, anteponendo il timestamp corrente alla stringa "create_tablename_table". L'estensione sarà ovviamente PHP.
Prima di andare avanti, dobbiamo ricordare che la best practice ci dice di utilizzare i nomi delle tabelle in inglese e al plurale. Quindi per la tabella degli articoli useremo posts, per le categorie useremo categories e così via.
Il timestamp viene apposto perché le migrations vengono eseguite secondo l’ordine temporale di creazione. E se abbiamo una migrazione che al suo interno ha una foreign key, la tabella relazionata deve essere generata a priori. Quindi potrebbe essere necessario rinominare i prefisso per una corretta esecuzione.
La classe della Migration
La classe della migrazione è composta da due metodi: up() e down(). Il primo è responsabile della creazione/modifica della tabella mentre il metodo down è responsabile del rollback, riportando il database alla situazione precedente alla migrazione. Quindi nella stragrande maggioranza dei casi si occupa di fare il drop della tabella generata nel metodo up.
Per quanto riguarda la definizione dei campi, non credo che io possa aggiungere molto rispetto alla documentazione ufficiale.
Quindi, ricapitolando: ogni file di migrazione rappresenta una singola serie di modifiche, come la creazione di una nuova tabella, l'aggiunta o la rimozione di colonne da una tabella esistente o l'aggiornamento del tipo di dati di una colonna. In ogni file di migrazione è presente un metodo che gestisce il rollback.
Una volta definite le migrazioni, possiamo utilizzare lo strumento da riga di comando Laravel Artisan per migrare le modifiche nello schema attuale.
php artistan migrate
In questo modo verranno eseguite tutte le migrazioni che non sono state ancora lanciate. Infatti Laravel gestisce questo sistema con un ulteriore tabella, "migrations" appunto. Ogni volta che viene lanciato il comando appena descritto, Laravel controlla dentro quella tabella e capisce quali sono le migrazioni che non hanno ancora girato e si regola di conseguenza.
Se volessimo lanciare tutte le migrazioni, bypassando questo controllo, dobbiamo azzerare la base dati in questo modo
php artisan migrate:fresh
Definire una relazione tra tabelle
Sicuramente avrai sentito parlare delle relazioni tra i modelli di Eloquent, l'ORM di Laravel. Ma quelle sono relazioni risolte lato software. Le relazioni in consistenza vanno definite nelle migrazioni.
Per definire una chiave esterna in una migrazione Laravel, come abbiamo visto, bisogna prima creare la migrazione relativa alla tabella che vogliamo relazionare.
Con un esempio pratico, supponiamo di aver creato una tabella chiamata cars, in cui la chiave primaria è il classico campo id di tipo int.
public function up()
{
Schema::create('cars', function (Blueprint $table) {
$table->increments('id');
$table->string('model', 45);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('cars');
}
Come possiamo vedere ho anche impostato il comando timestamps, che andrà a creare due campi, created_at e updated_at, popolati automaticamente rispettivamente alla creazione e alla modifica del record.
Ma torniamo a noi, adesso dobbiamo creiamo la migrazione per la tabella che vogliamo mettere in relazione, ad esempio owners. Inseriamo tutti i campi che vogliamo migrare e per quanto riguarda la foreign key dobbiamo usare il tipo unsigned. L'unsigned è un tipo di dato che impartisce al DBMS che non è possibile inserire numeri negativi. Quindi, ricordandoci del tipo di dato che abbiamo definito per la chiave primaria della tabella cars, la foreign key della tabella owners sarà così:
$table->unsignedInteger('car_id');
Adesso dobbiamo creare il vincolo, per prevenire interruzioni dei collegamenti tra le due tabelle. Useremo anche l'istruzione di eliminazione a cascata, in modo che quando un record della tabella padre verrà eliminato, i record corrispondenti della tabella figlio verranno automaticamente eliminati.
$table->foreign('car_id')->references('id')->on('cars')->onDelete('cascade');
Gestire le alter table
Con le migrazioni possiamo anche gestire le alter table. Anzi, è bene prendere dimestichezza con l'uso delle migrazioni per queste situazioni, in quanto quando siamo in produzione, non è possibile ridefinire lo schema droppando tutto il database.
Ad esempio supponiamo di voler aggiungere il campo color alla tabella cars. Dal terminale lanciamo il seguente
php artisan make:migration add_color_field_to_cars_table
Apriamo la classe appena generata e impostiamo i metodi up() e down() come di seguito
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('cars', function (Blueprint $table) {
$table->string('color');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('cars', function (Blueprint $table) {
$table->dropColumn('color');
});
}
Ok, a questo punto possiamo lanciare il comando per fare girare le migrations
Cancellazione logica
Laravel dispone di un meccanismo per escludere i records che vogliamo gestire come cancellati, senza effettivamente eliminarli dal database. Si parla in questo caso di cancellazione logica.
Vedremo in seguito come gestirli con il software. Per adesso ci basti sapere che quando un record ha il campo deleted_at popolato, il framework considera questo record come cancellato.
Quindi predisponiamo la nostra tabella cars con il softdelete.
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('cars', callback: function (Blueprint $table) {
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('cars', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
E cosa succede al vincolo delle foreign key quando un record della tabella padre viene cancellato con il soft delete? Questa è una situazione particolare che dovremo gestire. Ma dovremo farlo attraverso i metodi di Laravel, perché sulla base dati la relazione è viva e vegeta.