Skip to content

Migrations

Database migrations are version control for your database, allowing you to modify your database schema in a structured and organized way. AvelPress migrations are inspired by Laravel's migration system.

Introduction

Migrations allow you to:

  • Create and modify database tables
  • Add and remove columns
  • Create indexes and foreign keys
  • Seed initial data
  • Version control your database schema

Creating Migrations

Migration Files

Migration files are stored in the database/migrations directory and follow this naming convention:

YYYY_MM_DD_HHMMSS_description_of_migration.php

Basic Migration Structure

php
<?php

use AvelPress\Database\Migrations\Migration;
use AvelPress\Database\Schema\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up()
    {
        Schema::create('users', function ($table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
};

Table Operations

Creating Tables

Schema::create()

Create a new table.

php
Schema::create('posts', function ($table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->string('status')->default('draft');
    $table->timestamps();
});

Table Options

php
Schema::create('posts', function ($table) {
    $table->id();
    $table->string('title');
    // ... other columns
}, [
    'engine' => 'InnoDB',
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_unicode_ci'
]);

Modifying Tables

Schema::table()

Modify an existing table.

php
Schema::table('users', function ($table) {
    $table->string('phone')->nullable();
    $table->dropColumn('old_column');
    $table->renameColumn('old_name', 'new_name');
});

Dropping Tables

Schema::drop() / Schema::dropIfExists()

php
// Drop table (will fail if table doesn't exist)
Schema::drop('posts');

// Drop table if it exists
Schema::dropIfExists('posts');

Column Types

String Columns

php
$table->string('name');                    // VARCHAR(255)
$table->string('name', 100);               // VARCHAR(100)
$table->text('description');               // TEXT
$table->longText('content');               // LONGTEXT
$table->char('code', 10);                  // CHAR(10)

Numeric Columns

php
$table->integer('views');                  // INT
$table->bigInteger('big_number');          // BIGINT
$table->smallInteger('small_number');      // SMALLINT
$table->decimal('price', 8, 2);            // DECIMAL(8,2)
$table->float('rating', 3, 1);             // FLOAT(3,1)
$table->double('coordinates', 15, 8);      // DOUBLE(15,8)

Date and Time Columns

php
$table->date('birth_date');                // DATE
$table->time('start_time');                // TIME
$table->datetime('created_at');            // DATETIME
$table->timestamp('updated_at');           // TIMESTAMP
$table->timestamps();                      // created_at & updated_at

Boolean and Binary

php
$table->boolean('is_active');              // BOOLEAN
$table->binary('data');                    // BLOB

Special Columns

php
$table->id();                              // Auto-increment primary key
$table->uuid('uuid');                      // UUID column
$table->json('metadata');                  // JSON column (MySQL 5.7+)
$table->enum('status', ['draft', 'published', 'archived']);

Column Modifiers

Nullable and Default Values

php
$table->string('name')->nullable();        // Allow NULL
$table->string('status')->default('active'); // Default value
$table->integer('views')->default(0);
$table->timestamp('created_at')->useCurrent(); // Use CURRENT_TIMESTAMP

Indexes

php
$table->string('email')->unique();         // Unique index
$table->string('name')->index();           // Regular index
$table->index(['user_id', 'created_at']);  // Composite index
$table->index('email', 'idx_user_email');  // Named index

Auto Increment

php
$table->id();                              // Auto-increment primary key
$table->bigInteger('id')->autoIncrement()->primary(); // Custom auto-increment

Comments

php
$table->string('name')->comment('User full name');
$table->integer('views')->comment('Page view count');

Foreign Key Constraints

Basic Foreign Keys

php
// Add foreign key
$table->foreignId('user_id')->constrained();

// Custom foreign key
$table->foreignId('category_id')->constrained('categories');

// Foreign key with custom column
$table->unsignedBigInteger('author_id');
$table->foreign('author_id')->references('id')->on('users');

Foreign Key Actions

php
$table->foreignId('user_id')
    ->constrained()
    ->onUpdate('cascade')
    ->onDelete('cascade');

$table->foreignId('category_id')
    ->constrained()
    ->onDelete('set null');

Dropping Foreign Keys

php
Schema::table('posts', function ($table) {
    $table->dropForeign(['user_id']);
    $table->dropForeign('posts_user_id_foreign'); // By name
});

Indexes

Creating Indexes

php
Schema::table('users', function ($table) {
    $table->index('email');                 // Single column
    $table->index(['first_name', 'last_name']); // Composite
    $table->unique('username');             // Unique index
    $table->index('created_at', 'idx_created'); // Named index
});

Dropping Indexes

php
Schema::table('users', function ($table) {
    $table->dropIndex(['email']);          // By column
    $table->dropIndex('users_email_index'); // By name
    $table->dropUnique(['username']);      // Drop unique
});

Index Types

php
$table->index('title');                    // Regular index
$table->unique('email');                   // Unique index
$table->primary(['id', 'type']);           // Composite primary key
$table->fullText('content');               // Full-text index (MySQL)

Column Operations

Adding Columns

php
Schema::table('users', function ($table) {
    $table->string('phone')->nullable()->after('email');
    $table->text('bio')->after('name');
    $table->timestamp('verified_at')->nullable();
});

Modifying Columns

php
Schema::table('users', function ($table) {
    $table->string('name', 100)->change();  // Change length
    $table->string('email')->nullable()->change(); // Make nullable
    $table->renameColumn('name', 'full_name');
});

Dropping Columns

php
Schema::table('users', function ($table) {
    $table->dropColumn('phone');
    $table->dropColumn(['temp1', 'temp2']); // Multiple columns
});

WordPress Integration

Table Prefixes

AvelPress automatically handles WordPress table prefixes:

php
// This creates {wp_prefix}my_custom_table
Schema::create('my_custom_table', function ($table) {
    $table->id();
    $table->string('name');
});

WordPress-specific Columns

php
Schema::create('posts', function ($table) {
    $table->id();
    $table->string('post_title');
    $table->longText('post_content');
    $table->string('post_status', 20)->default('publish');
    $table->string('post_type', 20)->default('post');
    $table->unsignedBigInteger('post_author');
    $table->datetime('post_date');
    $table->datetime('post_modified');

    // Foreign key to WordPress users table
    $table->foreign('post_author')->references('ID')->on('users');
});

Integration with WordPress Tables

php
Schema::create('user_profiles', function ($table) {
    $table->id();
    $table->unsignedBigInteger('user_id');
    $table->text('bio')->nullable();
    $table->string('website')->nullable();
    $table->timestamps();

    // Reference WordPress users table
    $table->foreign('user_id')->references('ID')->on('users')->onDelete('cascade');
    $table->unique('user_id');
});

Running Migrations

Automatic Migration

Migrations can be run automatically when your plugin is activated:

php
// In your main plugin file
register_activation_hook(__FILE__, function() {
    $migrator = AvelPress::app('migrator');
    $migrator->run();
});

Manual Migration

php
// In your service provider or initialization code
$migrator = AvelPress::app('migrator');
$migrator->run();

Migration Status

php
$migrator = AvelPress::app('migrator');

// Check if migrations are pending
if ($migrator->hasPendingMigrations()) {
    // Run pending migrations
    $migrator->run();
}

// Get migration status
$status = $migrator->getStatus();

Advanced Features

Conditional Migrations

php
public function up()
{
    // Only create if table doesn't exist
    if (!Schema::hasTable('custom_table')) {
        Schema::create('custom_table', function ($table) {
            $table->id();
            $table->string('name');
        });
    }

    // Only add column if it doesn't exist
    if (!Schema::hasColumn('users', 'phone')) {
        Schema::table('users', function ($table) {
            $table->string('phone')->nullable();
        });
    }
}

Data Seeding in Migrations

php
public function up()
{
    Schema::create('categories', function ($table) {
        $table->id();
        $table->string('name');
        $table->string('slug');
        $table->timestamps();
    });

    // Seed initial data
    DB::table('categories')->insert([
        ['name' => 'Technology', 'slug' => 'technology', 'created_at' => now()],
        ['name' => 'Business', 'slug' => 'business', 'created_at' => now()],
        ['name' => 'Design', 'slug' => 'design', 'created_at' => now()],
    ]);
}

Raw SQL in Migrations

php
public function up()
{
    // Create table with raw SQL
    DB::statement('CREATE TABLE custom_table (
        id INT AUTO_INCREMENT PRIMARY KEY,
        data JSON,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )');

    // Add custom index
    DB::statement('CREATE INDEX idx_custom ON custom_table (data->"$.type")');
}

Best Practices

1. Always Provide Down Methods

php
public function up()
{
    Schema::create('posts', function ($table) {
        // Table definition
    });
}

public function down()
{
    Schema::dropIfExists('posts');
}

2. Use Descriptive Names

php
// Good
2024_01_15_143000_create_user_profiles_table.php
2024_01_15_144500_add_phone_to_users_table.php

// Bad
2024_01_15_143000_migration.php
2024_01_15_144500_update_users.php

3. Make Migrations Reversible

php
public function up()
{
    Schema::table('users', function ($table) {
        $table->string('phone')->nullable();
    });
}

public function down()
{
    Schema::table('users', function ($table) {
        $table->dropColumn('phone');
    });
}

4. Handle Large Data Sets

php
public function up()
{
    Schema::table('users', function ($table) {
        $table->string('full_name')->nullable();
    });

    // Update in chunks for large tables
    User::chunk(1000, function ($users) {
        foreach ($users as $user) {
            $user->update([
                'full_name' => $user->first_name . ' ' . $user->last_name
            ]);
        }
    });
}

5. Test Migrations

Always test your migrations in a development environment before deploying:

php
// Test up migration
$migrator->run();

// Test down migration
$migrator->rollback();

This migration system provides a robust way to manage your database schema changes while maintaining compatibility with WordPress conventions and best practices.

Released under the MIT License.