Laravel migrations are a powerful tool for managing your database schema, but they come with nuances that can make or break your application’s stability. Below are some core principles for writing migrations based on Kevin’s best practices.

Avoid Dynamic Table and Column Definitions

Using dynamic references, such as attaching table names to model properties, can lead to migration failures when your application evolves. For example, tying a migration to a model’s table name or column via a property like $table might seem DRY, but it introduces fragility.

Why This is Problematic

If the table name or column is updated in the model, older migrations tied to that reference will fail since the schema no longer aligns. Migrations need to be immutable records of the database structure at the time they were created.

Best Practice: Use Plain Text Table and Column Names

Define all table names and columns as plain text in migrations. Avoid referencing them dynamically.

// BAD: Using dynamic table name from the model
Schema::create(User::TABLE_NAME, function (Blueprint $table) {  
    $table->id();  
    $table->string(User::EMAIL_COLUMN);  
});

// GOOD: Explicit table and column names
Schema::create('users', function (Blueprint $table) {  
    $table->id();  
    $table->string('email');  
});

This ensures that even if User::TABLE_NAME changes, the migration remains valid.

Separate Schema Changes from Data Changes

A common mistake is to include data migrations (e.g., seeding data) in schema migrations. While it may seem convenient, mixing these responsibilities can create significant challenges, especially when rolling back or troubleshooting.

Why This is Problematic

Data migrations within schema migrations can:

  • Introduce dependencies that are hard to debug.
  • Fail silently or corrupt data during rollbacks.
  • Lead to inconsistencies between environments.

Best Practice: Use Commands for Data Migrations

Keep schema migrations purely for structural changes. Use Laravel’s artisan commands to handle data migrations.

Schema::create('roles', function (Blueprint $table) {  
    $table->id();  
    $table->string('name');  
});

Data Migration via Command:

// Command: php artisan add-default-roles
class AddDefaultRoles extends Command
{
    public function handle()
    {
        DB::table('roles')->insert([
            ['name' => 'Admin'],
            ['name' => 'Editor'],
            ['name' => 'Viewer'],
        ]);
        $this->info('Default roles added successfully.');
    }
}

This separation keeps your migrations clean and focused while providing data manipulation flexibility.

Block the down Method in Production

The down method is designed to reverse a migration, but rolling back changes on a live site can lead to data loss or critical failures. That works well with initial development and seeding but not for production. A safer approach is blocking rollbacks on production and creating a new migration for structural corrections.

Why This is Problematic

Rolling back a migration:

  • Can erase critical data if not carefully handled.
  • Often doesn’t account for live traffic or edge cases.

Best Practice: Disable down in Production

You can enforce this by adding a check in the migration base or overriding the down method to throw an exception in production environments.

// Example of blocking down in production
public function down()
{
    if (app()->environment('production')) {
        throw new \Exception('The "down" method is disabled in production.');
    }

    Schema::dropIfExists('roles');
}

Alternative Approach: Create a New Migration for Rollbacks

If you need to reverse a migration in production, write a new migration that accounts for preserving existing data.

// New migration to safely modify a table structure
Schema::table('users', function (Blueprint $table) {  
    $table->renameColumn('old_column', 'new_column');  
});

Conclusion

Adopting these best practices will save you headaches and ensure a robust migration workflow:

  • Use plain text for table and column names.
  • Separate schema changes from data changes using commands.
  • Avoid using down in production and handle rollbacks with explicit new migrations.

By following these principles, you’ll build migrations that are not only resilient but also maintainable across the lifecycle of your application. Happy coding!


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Get Involved & Explore More

an abstract painting with blue and yellow colors

Catch up on what I’ve been writing lately.

Show your gratitude.

Join Dare To Code Email List

Get emails from me on full-stack PHP development by subscribing to the Dare To Code mailing list.