Docs

Migration

Attribute-driven schema-diff engine and SQL migration runner built on the Entity & Database packages.

Point it at your entity metadata, and it will:

  1. Compare the current database schema with your annotated entities.

  2. Emit a timestamped migration class containing portable SQL (up() / down()).

  3. Track every run inside a _migrations table so nothing re-runs accidentally.

1 · Installation

composer require monkeyscloud/monkeyslegion-migration

Depends on the Core, DI, Entity, and Database packages you already installed.

2 · Generating Your First Migration

use MonkeysLegion\Migration\MigrationGenerator;
use MonkeysLegion\Entity\EntityScanner;
use MonkeysLegion\Database\Connection;

$entities   = (new EntityScanner([base_path('src/Domain')], $container))->scan();
$generator  = new MigrationGenerator(
    connection: $container->get(Connection::class),
    outDir:     base_path('database/migrations'),
);

$path = $generator->generate($entities);
echo "Created $path\n";       // e.g. 2025_06_11_180000_CreatePostsTable.php

The file contains:

final class Migration_2025_06_11_180000
{
    public function up(PDO $db): void   { /* … create table SQL … */ }
    public function down(PDO $db): void { /* … drop table SQL …   */ }
}

Tip: Commit generated files—never the _migrations table—to Git so teammates pull the same history.

3 · Running & Rolling Back

# apply everything that hasn’t run yet
php vendor/bin/ml migrate

# step back one batch
php vendor/bin/ml migrate:rollback

# view status
php vendor/bin/ml migrate:status

All commands share one PDO connection, so BEGIN … COMMIT wraps each batch automatically; if any SQL fails the batch rolls back.

4 · Writing Manual Migrations

Need data transforms or raw DDL? Create a class by hand:

// database/migrations/2025_06_11_190000_AddSlugToPosts.php
final class Migration_2025_06_11_190000
{
    public function up(PDO $db): void
    {
        $db->exec('ALTER TABLE posts ADD slug VARCHAR(160) AFTER title');
        $db->exec('UPDATE posts SET slug = LOWER(REPLACE(title, " ", "-"))');
    }

    public function down(PDO $db): void
    {
        $db->exec('ALTER TABLE posts DROP COLUMN slug');
    }
}

The runner picks it up automatically based on filename.

5 · Events Integration

When a batch completes, the package dispatches MigrationEvent (from monkeyslegion-events).

Listeners receive:

MigrationEvent {
    public readonly string $direction;   // 'up' | 'down'
    public readonly array  $files;       // migration class names
    public readonly float  $time;        // execution time in seconds
}

Perfect for Slack notifications or Prometheus counters.

6 · DI Registration Snippet

return [
    MigrationGenerator::class => fn($c) => new MigrationGenerator(
        connection: $c->get(MonkeysLegion\Database\Connection::class),
        outDir:     base_path('database/migrations'),
    ),
];

The CLI commands resolve both the generator and the database connection from the container, so no duplicated config.

7 · Roadmap

  • SQLite & PostgreSQL dialects (MySQL 8.4 is supported today).

  • Column-rename detection (currently treated as drop + add).

  • migrate:fresh command—drop all tables and re-run head.

License

MIT © 2025 MonkeysCloud