Files
michaelschiemer/docs/claude/migration-helper-examples.md
2025-11-24 21:28:25 +01:00

9.8 KiB

Migration Helper Examples

This document shows how to use the new MigrationHelper service to simplify migration code and explains the improved migration generation system.

Overview

The MigrationHelper provides a simplified API for common migration operations without requiring abstract classes. It uses composition and follows framework principles.

The improved MigrationGenerator now:

  • Automatically determines namespace from file path using PathProvider
  • Uses PhpNamespace Value Object for type-safe namespace handling
  • Automatically detects if migration should be SafelyReversible based on name patterns
  • Generates cleaner code using MigrationHelper by default

Basic Usage

Before (Old Way)

final readonly class CreateUsersTable implements Migration
{
    public function up(ConnectionInterface $connection): void
    {
        $schema = new Schema($connection);
        $schema->createIfNotExists('users', function (Blueprint $table) {
            $table->ulid('ulid')->primary();
            $table->string('name', 255);
            $table->timestamps();
        });
        $schema->execute();
    }
    
    public function getVersion(): MigrationVersion
    {
        return MigrationVersion::fromTimestamp('2024_01_15_000001');
    }
    
    public function getDescription(): string
    {
        return 'Create Users Table';
    }
}

After (New Way with Helper)

final readonly class CreateUsersTable implements Migration, SafelyReversible
{
    public function up(ConnectionInterface $connection): void
    {
        $helper = new MigrationHelper($connection);
        $helper->createTable('users', function (Blueprint $table) {
            $table->ulid('ulid')->primary();
            $table->string('name', 255);
            $table->timestamps();
        });
    }
    
    public function down(ConnectionInterface $connection): void
    {
        $helper = new MigrationHelper($connection);
        $helper->dropTable('users');
    }
    
    public function getVersion(): MigrationVersion
    {
        return MigrationVersion::fromTimestamp('2024_01_15_000001');
    }
    
    public function getDescription(): string
    {
        return 'Create Users Table';
    }
}

Available Helper Methods

Creating Tables

$helper = new MigrationHelper($connection);

$helper->createTable('users', function (Blueprint $table) {
    $table->ulid('ulid')->primary();
    $table->string('name', 255);
    $table->string('email', 255)->unique();
    $table->timestamps();
});

Dropping Tables

$helper->dropTable('users');

Adding Columns

// Simple column
$helper->addColumn('users', 'phone', 'string', ['length' => 20]);

// Column with options
$helper->addColumn('users', 'age', 'integer', [
    'nullable' => false,
    'default' => 0
]);

// Decimal column
$helper->addColumn('orders', 'total', 'decimal', [
    'precision' => 10,
    'scale' => 2,
    'nullable' => false
]);

Adding Indexes

// Simple index
$helper->addIndex('users', ['email']);

// Named index
$helper->addIndex('users', ['name', 'email'], 'idx_users_name_email');

// Unique index
$helper->addUniqueIndex('users', ['email'], 'uk_users_email');

Dropping Columns

$helper->dropColumn('users', 'phone');

Renaming Tables

$helper->renameTable('old_table_name', 'new_table_name');

Advanced: Using Schema Directly

For complex operations, you can still use Schema directly:

public function up(ConnectionInterface $connection): void
{
    $helper = new MigrationHelper($connection);
    $schema = $helper->schema();
    
    // Complex operations
    $schema->table('users', function (Blueprint $table) {
        $table->dropColumn('old_column');
        $table->renameColumn('old_name', 'new_name');
        $table->addColumn('new_column', 'string', 255);
    });
    
    $schema->execute();
}

Benefits

  1. Less Boilerplate: No need to manually create Schema and call execute()
  2. Consistent API: All helper methods follow the same pattern
  3. Type Safe: Uses Value Objects and proper types
  4. Composable: Can mix helper methods with direct Schema usage
  5. No Inheritance: Uses composition, follows framework principles

Migration Generator

The MigrationGenerator has been significantly improved with automatic namespace detection and smarter code generation.

Automatic Namespace Detection

The generator uses PathProvider to automatically determine the correct namespace from the file path, making it work with any project structure:

// When generating: php console.php make:migration CreateUsersTable User
// Path: src/Domain/User/Migrations/CreateUsersTable.php
// Namespace automatically determined: App\Domain\User\Migrations

How it works:

  1. PathProvider reads composer.json to understand PSR-4 autoloading rules
  2. The migration directory path is analyzed
  3. PhpNamespace Value Object is created from the path
  4. Fallback to default structure if path doesn't match PSR-4 rules

Benefits:

  • Works with any namespace structure configured in composer.json
  • No hardcoded namespace assumptions
  • Type-safe namespace handling via PhpNamespace Value Object
  • Automatically adapts to project structure changes

Automatic SafelyReversible Detection

The generator analyzes migration names to determine if they should implement SafelyReversible:

Safe patterns (automatically reversible):

  • Create* - Creating tables can be rolled back
  • Add* - Adding columns/indexes can be removed
  • Index* - Index operations are reversible
  • Constraint* - Constraints can be dropped
  • Rename* - Renaming can be reversed

Unsafe patterns (forward-only):

  • Drop* - Dropping tables/columns loses data
  • Delete* - Data deletion cannot be reversed
  • Remove* - Removing columns loses data
  • Truncate* - Truncating loses all data
  • Alter*Type - Type changes may lose data

Example:

# Creates SafelyReversible migration
php console.php make:migration CreateUsersTable User

# Creates forward-only migration
php console.php make:migration DropOldColumns User

Generated Code Examples

Create Table Migration (SafelyReversible):

php console.php make:migration CreateUsersTable User

Generates:

<?php

declare(strict_types=1);

namespace App\Domain\User\Migrations;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\Migration\Services\MigrationHelper;
use App\Framework\Database\Migration\SafelyReversible;

final readonly class CreateUsersTable implements Migration, SafelyReversible
{
    public function up(ConnectionInterface $connection): void
    {
        $helper = new MigrationHelper($connection);

        // TODO: Implement your migration here
        // Example:
        // $helper->createTable('users', function($table) {
        //     $table->ulid('ulid')->primary();
        //     $table->string('name', 255);
        //     $table->timestamps();
        // });
    }

    public function down(ConnectionInterface $connection): void
    {
        $helper = new MigrationHelper($connection);
        $helper->dropTable('users'); // Auto-generated from name
    }

    public function getVersion(): MigrationVersion
    {
        return MigrationVersion::fromTimestamp('2024_01_15_120000');
    }

    public function getDescription(): string
    {
        return 'Create Users Table';
    }
}

Forward-Only Migration:

php console.php make:migration DropOldColumns User

Generates:

<?php

declare(strict_types=1);

namespace App\Domain\User\Migrations;

use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\Migration\Migration;
use App\Framework\Database\Migration\MigrationVersion;
use App\Framework\Database\Migration\Services\MigrationHelper;

final readonly class DropOldColumns implements Migration
{
    public function up(ConnectionInterface $connection): void
    {
        $helper = new MigrationHelper($connection);

        // TODO: Implement your migration here
        // Example:
        // $helper->dropColumn('users', 'old_column');
    }

    // No down() method - forward-only migration

    public function getVersion(): MigrationVersion
    {
        return MigrationVersion::fromTimestamp('2024_01_15_120001');
    }

    public function getDescription(): string
    {
        return 'Drop Old Columns';
    }
}

Custom Namespace Structures

The generator works with any namespace structure defined in composer.json:

{
  "autoload": {
    "psr-4": {
      "App\\": "src/",
      "Custom\\Namespace\\": "custom/path/"
    }
  }
}

If you generate a migration in custom/path/Migrations/, the namespace will automatically be Custom\Namespace\Migrations.

MigrationMetadata Value Object

For advanced use cases, you can use MigrationMetadata to work with migration metadata:

use App\Framework\Database\Migration\ValueObjects\MigrationMetadata;
use App\Framework\Core\ValueObjects\PhpNamespace;

$namespace = PhpNamespace::fromString('App\Domain\User\Migrations');
$metadata = MigrationMetadata::create(
    version: MigrationVersion::fromTimestamp('2024_01_15_120000'),
    description: 'Create Users Table',
    namespace: $namespace,
    domain: 'User',
    author: 'John Doe'
);

// Metadata automatically extracts domain from namespace
// $metadata->domain === 'User'

Best Practices

  1. Use MigrationHelper for common operations - Reduces boilerplate
  2. Let generator detect SafelyReversible - Don't manually add it unless needed
  3. Trust PathProvider for namespaces - It handles PSR-4 correctly
  4. Use PhpNamespace Value Objects - Type-safe namespace operations
  5. Keep migrations simple - Use helper methods, fall back to Schema for complex cases