Skip to content

BaseMigration ​

Migration base class with automatic table prefixing.

Overview ​

BaseMigration extends Laravel's Migration class to automatically handle table prefixes according to Twist configuration. This ensures consistent table naming across all addon migrations.

Key Features ​

  • Automatic Prefix Application: Uses configured table prefixes
  • Consistent Naming: Ensures all addon tables follow naming conventions
  • Configuration Aware: Respects Twist configuration settings
  • Postfix Support: Additional naming flexibility

Basic Usage ​

Simple Migration ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'users');
    }
};

Migration with Postfix ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    protected string $postfix = 'admin_';

    public function up(): void
    {
        Schema::create($this->prefix . 'settings', function (Blueprint $table) {
            $table->id();
            $table->string('key')->unique();
            $table->text('value')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'settings');
    }
};

Migration with Foreign Keys ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('content');
            $table->foreignId('user_id')->constrained($this->prefix . 'users');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'posts');
    }
};

Properties ​

$prefix ​

Automatically set table prefix based on configuration. Read-only after construction.

php
protected string $prefix = '';

$postfix ​

Additional string to insert between the configuration prefix and table name.

php
protected string $postfix = '';

Example:

php
protected string $postfix = 'module_';
// Results in: twist_module_tablename

Advanced Usage ​

Index Creation ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('phone')->nullable();
            $table->timestamps();

            // Indexes
            $table->unique('email');
            $table->index(['name', 'email']);
            $table->index('phone');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'users');
    }
};

Complex Relationships ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'user_roles', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')
                  ->constrained($this->prefix . 'users')
                  ->onDelete('cascade');
            $table->foreignId('role_id')
                  ->constrained($this->prefix . 'roles')
                  ->onDelete('cascade');
            $table->timestamps();

            $table->unique(['user_id', 'role_id']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'user_roles');
    }
};

JSON Columns ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'user_preferences', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained($this->prefix . 'users');
            $table->json('settings');
            $table->json('permissions')->nullable();
            $table->timestamps();

            // JSON indexes (MySQL 5.7+)
            $table->index('settings->theme');
            $table->index('settings->language');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'user_preferences');
    }
};

Conditional Schema Changes ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'analytics', function (Blueprint $table) {
            $table->id();
            $table->string('event');
            $table->json('data');
            $table->timestamp('occurred_at');

            // Add partition support for MySQL
            if (DB::connection()->getDriverName() === 'mysql') {
                $table->index('occurred_at');
            }

            // PostgreSQL specific features
            if (DB::connection()->getDriverName() === 'pgsql') {
                $table->uuid('uuid')->unique();
            }
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'analytics');
    }
};

Migration Patterns ​

Tenant-Aware Tables ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'tenant_users', function (Blueprint $table) {
            $table->id();
            $table->string('tenant_id');
            $table->string('name');
            $table->string('email');
            $table->timestamps();

            // Ensure email is unique per tenant
            $table->unique(['tenant_id', 'email']);
            $table->index('tenant_id');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'tenant_users');
    }
};

Versioned Data ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'document_versions', function (Blueprint $table) {
            $table->id();
            $table->foreignId('document_id')->constrained($this->prefix . 'documents');
            $table->integer('version_number');
            $table->text('content');
            $table->foreignId('created_by')->constrained($this->prefix . 'users');
            $table->timestamps();

            $table->unique(['document_id', 'version_number']);
            $table->index(['document_id', 'created_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'document_versions');
    }
};

Soft Delete Support ​

php
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Twist\Base\BaseMigration;

return new class extends BaseMigration
{
    public function up(): void
    {
        Schema::create($this->prefix . 'products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description');
            $table->decimal('price', 10, 2);
            $table->boolean('active')->default(true);
            $table->timestamps();
            $table->softDeletes();

            $table->index(['active', 'deleted_at']);
        });
    }

    public function down(): void
    {
        Schema::dropIfExists($this->prefix . 'products');
    }
};

Configuration Integration ​

Prefix Resolution ​

The prefix is resolved in this order:

  1. config('obelaw.database.table_prefix')
  2. Twist::getPrefixTable()
  3. Default empty string

Environment-Specific Prefixes ​

php
// .env
TWIST_TABLE_PREFIX=dev_twist_
OBELAW_DB_PREFIX=staging_
php
// config/twist.php
'database' => [
    'table_prefix' => env('TWIST_TABLE_PREFIX', 'twist_'),
],

Testing Migrations ​

Migration Testing ​

php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;

class UserMigrationTest extends TestCase
{
    use RefreshDatabase;

    public function test_users_table_creation(): void
    {
        $prefix = config('twist.database.table_prefix', 'twist_');
        $tableName = $prefix . 'users';

        $this->assertTrue(Schema::hasTable($tableName));
        $this->assertTrue(Schema::hasColumn($tableName, 'id'));
        $this->assertTrue(Schema::hasColumn($tableName, 'name'));
        $this->assertTrue(Schema::hasColumn($tableName, 'email'));
    }

    public function test_users_table_indexes(): void
    {
        $prefix = config('twist.database.table_prefix', 'twist_');
        $tableName = $prefix . 'users';

        $indexes = Schema::getIndexes($tableName);
        $indexNames = array_column($indexes, 'name');

        $this->assertContains($tableName . '_email_unique', $indexNames);
    }
}

Database Factories with Prefixed Tables ​

php
<?php

namespace Database\Factories;

use App\Addons\UserManagement\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition(): array
    {
        return [
            'name' => $this->faker->name(),
            'email' => $this->faker->unique()->safeEmail(),
            'password' => bcrypt('password'),
        ];
    }
}

Best Practices ​

1. Consistent Naming ​

php
// Use snake_case for table names
Schema::create($this->prefix . 'user_profiles', function (Blueprint $table) {
    // Table structure
});

// Use descriptive names
Schema::create($this->prefix . 'user_login_attempts', function (Blueprint $table) {
    // Better than just 'attempts'
});

2. Foreign Key Constraints ​

php
// Always use constrained() with prefixed tables
$table->foreignId('user_id')->constrained($this->prefix . 'users');

// Specify cascade behavior
$table->foreignId('user_id')
      ->constrained($this->prefix . 'users')
      ->onDelete('cascade');

3. Index Strategy ​

php
// Index foreign keys
$table->foreignId('user_id')->constrained($this->prefix . 'users');
$table->index('user_id'); // Usually automatic, but explicit is better

// Composite indexes for common queries
$table->index(['user_id', 'created_at']);
$table->index(['status', 'priority']);

4. Down Method Implementation ​

php
// Always implement proper down methods
public function down(): void
{
    // Drop foreign key constraints first if needed
    Schema::table($this->prefix . 'posts', function (Blueprint $table) {
        $table->dropForeign(['user_id']);
    });

    Schema::dropIfExists($this->prefix . 'posts');
}

Common Patterns ​

Addon Migration Structure ​

addon/
├── Database/
│   └── Migrations/
│       ├── 2024_01_01_000001_create_users_table.php
│       ├── 2024_01_01_000002_create_roles_table.php
│       └── 2024_01_01_000003_create_user_roles_table.php

Migration Dependencies ​

php
// When migrations depend on each other, use clear naming
// 2024_01_01_000001_create_users_table.php
// 2024_01_01_000002_create_roles_table.php  
// 2024_01_01_000003_create_user_roles_table.php (depends on both above)

Next Steps ​

Released under the MIT License.