Introductie
Migrations vormen de versiebeheer voor je databaseschema. Goed geschreven migrations zijn gericht, omkeerbaar en bevatten vanaf het begin de juiste indexering. Omdat migrations bevroren momentopnames zijn, vragen ze om speciale discipline, zodra ze naar productie zijn uitgerold, mogen ze nooit meer worden gewijzigd.
Waarom
- Consistentie: Het gebruik van
constrained()voor foreign keys zorgt voor automatische naamgeving en referentiële integriteit - Veiligheid: Uitgerolde migrations nooit wijzigen voorkomt inconsistente databasetoestanden tussen omgevingen
- Performance: Indexes toevoegen in de migration in plaats van achteraf voorkomt vergeten performance-optimalisaties
- Omkeerbaarheid: Het schrijven van
down()-methodes maakt veilige rollbacks mogelijk tijdens mislukte deployments en in CI-pipelines - Duidelijkheid: Eén verantwoordelijkheid per migration maakt het eenvoudig om te herkennen wat er wanneer is veranderd
Geschikt voor
- Alle Laravel-applicaties die migrations gebruiken
- Teams met meerdere developers die aan hetzelfde databaseschema werken
- Projecten met CI/CD-pipelines die migrations uitvoeren
Minder geschikt voor
- N.v.t., deze praktijken gelden voor elk project dat Laravel-migrations gebruikt
Voorbeelden
Gebruik constrained() voor foreign keys
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
// Non-standard names
$table->foreignId('author_id')->constrained('users');
Wijzig uitgerolde migrations nooit
// Bad: editing a migration that already ran in production
// 2024_01_01_create_posts_table.php
$table->string('slug')->unique(); // added after deployment
// Good: new migration to alter the table
// 2024_03_15_add_slug_to_posts_table.php
Schema::table('posts', function (Blueprint $table) {
$table->string('slug')->unique()->after('title');
});
Voeg indexes toe in de migration
// Bad: no indexes on frequently queried columns
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('status');
$table->timestamps();
});
// Good: indexes added from the start
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->index();
$table->string('status')->index();
$table->timestamp('shipped_at')->nullable()->index();
$table->timestamps();
});
Spiegel column-defaults in model $attributes
Wanneer een column een database-default heeft, spiegel deze dan in het model zodat nieuwe instances de juiste waarden hebben vóór het opslaan:
// Migration
$table->string('status')->default('pending');
// Model
protected $attributes = [
'status' => 'pending',
];
Schrijf omkeerbare down()-methodes
public function down(): void
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('slug');
});
}
Voor bewust onomkeerbare migrations laat je een duidelijke comment achter en vereis je in plaats daarvan een corrigerende voorwaartse migration.
Houd migrations gericht
Meng nooit DDL (schemawijzigingen) en DML (datamanipulatie) in één migration:
// Bad: partial failure creates unrecoverable state
public function up(): void
{
Schema::create('settings', function (Blueprint $table) { /* ... */ });
DB::table('settings')->insert(['key' => 'version', 'value' => '1.0']);
}
// Good: separate migrations
// Migration 1: create_settings_table
Schema::create('settings', function (Blueprint $table) { /* ... */ });
// Migration 2: seed_default_settings
DB::table('settings')->insert(['key' => 'version', 'value' => '1.0']);