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
PhpNamespaceValue Object for type-safe namespace handling - Automatically detects if migration should be
SafelyReversiblebased on name patterns - Generates cleaner code using
MigrationHelperby 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
- Less Boilerplate: No need to manually create Schema and call execute()
- Consistent API: All helper methods follow the same pattern
- Type Safe: Uses Value Objects and proper types
- Composable: Can mix helper methods with direct Schema usage
- 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:
PathProviderreadscomposer.jsonto understand PSR-4 autoloading rules- The migration directory path is analyzed
PhpNamespaceValue Object is created from the path- 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
PhpNamespaceValue 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 backAdd*- Adding columns/indexes can be removedIndex*- Index operations are reversibleConstraint*- Constraints can be droppedRename*- Renaming can be reversed
Unsafe patterns (forward-only):
Drop*- Dropping tables/columns loses dataDelete*- Data deletion cannot be reversedRemove*- Removing columns loses dataTruncate*- Truncating loses all dataAlter*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
- Use MigrationHelper for common operations - Reduces boilerplate
- Let generator detect SafelyReversible - Don't manually add it unless needed
- Trust PathProvider for namespaces - It handles PSR-4 correctly
- Use PhpNamespace Value Objects - Type-safe namespace operations
- Keep migrations simple - Use helper methods, fall back to Schema for complex cases