mongoose-studio/phobos-framework-database-migrations

Phobos Framework — Migraciones y seeders con el DSL Harmonia, agnóstico multi-motor (MySQL, PostgreSQL, SQLite)

Maintainers

Package info

github.com/mongoose-studio/phobos-framework-database-migrations

pkg:composer/mongoose-studio/phobos-framework-database-migrations

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-07-18 04:33 UTC

This package is auto-updated.

Last update: 2026-07-18 04:39:32 UTC


README

PHP Version License Phobos Framework

Phobos Framework

Migraciones y seeders para Phobos Framework Database Layer, con un DSL agnóstico de motor llamado Harmonia que compila a MySQL, PostgreSQL y SQLite desde una única definición. Se para sobre mongoose-studio/phobos-framework-database.

Coherente con la filosofía database-first de Phobos: la base de datos es la fuente de la verdad. Las migraciones versionan el esquema de forma explícita, y el Introspector hace el camino inverso — reconstruye la definición leyendo el catálogo de una base viva. Como Harmonia es una representación intermedia universal, un esquema se traduce entre motores: DB ⇄ Harmonia ⇄ SQL (cualquier motor).

Características

  • 🎯 DSL agnóstico - Un Blueprint fluido compila a MySQL, PostgreSQL y SQLite
  • 🧬 Convenciones Phobos - uuidv7() (calza con keyStrategy = uuidv7) y auditColumns() en una línea
  • 🔀 Escotilla híbrida - rawType() y Schema::raw() para lo específico de un motor
  • 📦 Harmonia - El DSL serializa a JSON versionado y portable, con modo estricto anti-inyección
  • 🔎 Introspección - DB → Harmonia: reconstruye definiciones desde una base viva y migra entre motores
  • 🔬 Diff de esquemas - Compara dos bases del mismo motor (dev↔prod, tenant↔golden) y exporta el delta a Harmonia/SQL/migración
  • 🌱 Seeders idempotentes - insertOrIgnore / upsert traducidos por motor
  • 🧾 Dry-run / SQL plano - Exporta el DDL sin ejecutar nada, incluso 100% offline
  • 🗂️ Multi-tenant - El Migrator recibe la conexión; aplicar a N tenants es recorrer conexiones
  • 🧮 Tabla de control - phobos_migrations con batch (rollback por lote) y checksum (detecta drift)

Instalación

composer require mongoose-studio/phobos-framework-database-migrations

Dependencias

Este paquete requiere:

  • mongoose-studio/phobos-framework-database ^3.2 (capa de base de datos)
  • El/los driver(s) que uses: -mysql, -postgres y/o -sqlite
  • PHP 8.4+ y la extensión ext-pdo

Una migración

Cada archivo retorna una clase anónima que extiende Migration (sin colisión de nombres, calza con el prefijo de timestamp del archivo):

<?php
use PhobosFramework\Migrations\Migration;
use PhobosFramework\Migrations\Schema\Schema;
use PhobosFramework\Migrations\Schema\Blueprint;

return new class extends Migration {
    public function up(): void
    {
        Schema::create('cuentas', function (Blueprint $t) {
            $t->uuidv7('id')->primary();          // calza con keyStrategy = "uuidv7"
            $t->string('codigo', 32)->unique();
            $t->string('nombre', 160);
            $t->uuid('cuenta_padre_id')->nullable();
            $t->boolean('imputable')->default(true);
            $t->json('meta')->nullable();          // jsonb · json · TEXT según el motor
            $t->auditColumns();                     // convención Phobos, en una línea
            $t->foreign('cuenta_padre_id')->references('id')->on('cuentas')->nullOnDelete();
            $t->index('nombre');
        });
    }

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

Portabilidad agnóstica (mapeo por motor)

DSL PostgreSQL MySQL SQLite
uuidv7() / uuid() uuid char(36) text
json() jsonb json text
boolean() boolean tinyint(1) integer
bigIncrements() bigserial bigint … auto_increment integer pk autoincrement
timestamp() timestamptz timestamp text

Escotilla híbrida para lo específico de un motor:

$t->json('meta')->rawType('pgsql', 'jsonb');      // tipo crudo por motor
Schema::raw("CREATE INDEX ix_meta ON cuentas USING gin (meta)", 'pgsql'); // no-op en otros

Harmonia — el DSL como formato JSON portable

El Blueprint es un modelo de datos separado de la compilación, así que la definición se serializa e importa en el formato Harmonia (JSON con marca de versión). Una definición → SQL de cualquier motor.

use PhobosFramework\Migrations\Schema\Harmonia;

// DSL → Harmonia (JSON)
$json = Schema::toJson('cuentas', function (Blueprint $t) {
    $t->uuidv7('id')->primary();
    $t->string('codigo', 32)->unique();
    $t->json('meta')->nullable();
});

// Harmonia (JSON) → aplicar (ESTRICTO por defecto)
Schema::fromJson($json);

// A bajo nivel:
$bp = Harmonia::decode($json);        // estricto
$json = Harmonia::encode($bp);

Modo estricto (import de fuentes no confiables)

Schema::fromJson() y Harmonia::decode() son estrictos por defecto: rechazan la escotilla rawType, tipos/comandos fuera de la whitelist e identificadores que no sean nombres simples — cerrando la vía de inyección de SQL. Para tu propio round-trip con rawType, usa strict: false explícito:

Schema::fromJson($jsonDeConfianza, strict: false);   // permite rawType

Aviso de seguridad: un archivo de migración PHP es código de confianza y ejecuta cualquier SQL (Schema::raw, db()->execute). La defensa es de proceso (PR + branch protegida

  • rol DB de least-privilege). Para aceptar definiciones de terceros, usa Harmonia en modo estricto (declarativo, sin código arbitrario).

Seeders idempotentes (sin duplicar)

  • Reference data de una sola vez → escríbelo como migración (corre una vez, tracked).
  • Catálogos re-sembrables / dev → helpers idempotentes:
use PhobosFramework\Migrations\Seeder;

class PermisosSeeder extends Seeder {
    public function run(): void {
        $this->insertOrIgnore('permissions', [
            ['code' => 'finco.cuentas.view'],
            ['code' => 'finco.cuentas.manage'],
        ]);

        $this->upsert('plans',
            [['code' => 'starter', 'rate_limit' => 500]],
            uniqueBy: ['code'],
        );
    }
}

insertOrIgnore / upsert se traducen a INSERT IGNORE / ON CONFLICT / ON DUPLICATE KEY según el motor.

CLI

Crea un phobos-migrations.php en la raíz que registre las conexiones (dbConfig) y retorne las rutas:

<?php
require __DIR__ . '/vendor/autoload.php';

use PhobosFramework\Database\Drivers\Postgres\PostgresDriver;

dbConfig(
    connections: ['main' => [ /* ... driver, host, database, ... */ ]],
    drivers: ['pgsql' => new PostgresDriver()],
    default: 'main',
);

return [
    'path'       => __DIR__ . '/database/migrations',
    'connection' => null,          // null = conexión por defecto
    'seeders'    => __DIR__ . '/database/seeders',
];
phobos-migrate make crear_cuentas   # 20260717HHMMSS_crear_cuentas.php
phobos-migrate migrate              # aplica pendientes
phobos-migrate rollback             # revierte el último batch
phobos-migrate status               # aplicada / pendiente / DRIFT
phobos-migrate fresh                # reset + migrate (dev)
phobos-migrate seed App\\Seeders\\PermisosSeeder

# Salida a SQL plano (no ejecuta nada)
phobos-migrate migrate --pretend           # SQL de las pendientes
phobos-migrate sql --driver=pgsql          # offline: compila todo a Postgres
phobos-migrate sql --driver=mysql > s.sql  # exportar a archivo

Introspección — DB → Harmonia (el inverso)

Coherente con database-first: la DB es la fuente de la verdad. El Introspector lee el catálogo y reconstruye la definición.

use PhobosFramework\Migrations\Introspection\Introspector;

$intro = Introspector::for();            // motor de la conexión activa
$intro->tables();                         // ['cuentas', ...]
$json = $intro->toHarmonia('cuentas');    // DB → Harmonia (JSON)
$bp   = $intro->table('cuentas');         // DB → Blueprint

Como Harmonia es el IR universal, recompilas a otro motor (migrar un esquema de un motor a otro):

$bp = Introspector::for('mysql_src')->table('cuentas');
$pg = Schema::pretendFor('pgsql', fn() => Schema::build($bp)); // MySQL → Postgres

CLI:

phobos-migrate import --table=cuentas              # → Harmonia (JSON)
phobos-migrate import --table=cuentas --sql=pgsql  # → SQL de Postgres
phobos-migrate import --to=migration               # congela la DB en migraciones

Fidelidad best-effort: PostgreSQL recupera la mayor riqueza (uuid, jsonb→json, boolean, timestamptz, bigserial→bigIncrements). SQLite es lossy por su afinidad de tipos (uuid/json/boolean ≈ text/integer) salvo que se hayan declarado con esos nombres. Los defaults se preservan verbatim (rawDefault, no portable) excepto los booleanos, que se normalizan a true/false.

Diff de esquemas (same-engine)

Compara dos esquemas completos del mismo motor y produce el delta estructural: tablas agregadas/eliminadas y, por cada tabla común, columnas +/-/~, índices, uniques, FKs y PK. Reusa el Introspector (DB→Blueprint), así que el delta se exporta a Harmonia, a SQL o a un archivo de migración.

use PhobosFramework\Migrations\Schema\Diff\SchemaDiff;
use PhobosFramework\Migrations\Schema\Diff\SchemaSnapshot;

// Dos fotos del esquema (introspectando conexiones vivas)
$diff = SchemaDiff::connections('prod', 'dev');   // o SchemaDiff::between($snapA, $snapB)

echo $diff->report();          // reporte legible (+/-/~ por tabla y columna)
$diff->toSql('pgsql');         // el delta aplicable como SQL
$diff->toHarmonia();           // el delta aplicable como Harmonia (JSON)
$diff->notes();                // lo que hay que revisar a mano (ver abajo)
phobos-migrate diff --from=prod --to=dev              # reporte
phobos-migrate diff --from=prod --to=dev --sql        # el delta como SQL
phobos-migrate diff --from=golden --to=tenant_42 --migration  # delta → archivo de migración

Same-engine a propósito. El diff cross-engine (con equivalencia de tipos char(36)≈uuid, tinyint(1)≈boolean, …) es ambiguo y quiere resolución visual con un humano en el loop → se reserva para Deimos Studio. Si las dos fotos declaran motores distintos, el diff se rechaza.

Subconjunto aplicable. El export cubre lo que el ALTER del DSL sabe compilar: crear tablas, agregar columnas/índices/FKs y eliminar columnas. Lo demás —modificar columnas, uniques nuevas, cambios de PK y lo destructivo (drops)— se reporta en notes() para revisión manual, no se aplica solo.

Multi-tenant

El Migrator recibe el nombre de conexión, así que aplicar a N bases de tenant es recorrer sus conexiones:

use PhobosFramework\Migrations\Migrator;

foreach ($tenantConnections as $name) {
    (new Migrator(__DIR__ . '/database/migrations', $name))->run();
}

Tabla de control

phobos_migrations: version (timestamp), batch (para rollback por lote), checksum (detecta drift si editas una migración ya aplicada), applied_at.

Notas por motor

  • PostgreSQL / SQLite: DDL transaccional — una migración fallida revierte por completo.
  • MySQL: el DDL hace commit implícito; una migración a medias puede dejar estado parcial (parte cada cambio grande en migraciones pequeñas).
  • SQLite: ALTER es limitado (agregar/eliminar columna sí; modificar columna requiere reconstruir la tabla — roadmap).

Tests

Suite PHPUnit (^11) — la lógica pura corre sin base de datos; el resto sobre SQLite :memory:.

composer install
composer test        # PHPUnit
composer analyze     # PHPStan (nivel 8, sin errores)

El tests/bootstrap.php también funciona sin composer (autoloader manual de los paquetes hermanos del monorepo), así que se puede correr con un phpunit.phar directo. Cubre: DSL/Blueprint, Harmonia (incluida la seguridad del modo estricto), las 3 gramáticas, el Migrator (run/rollback/status/pretend/drift), la introspección, el diff de esquemas y los seeders idempotentes. El src/ completo pasa PHPStan nivel 8 sin errores.

Compatibilidad

  • MySQL/MariaDB, PostgreSQL 12+, SQLite 3
  • PHP 8.4+

Licencia

Este proyecto está licenciado bajo la Licencia MIT - ver el archivo LICENSE para más detalles.

Autor

Marcel Rojas
marcelrojas16@gmail.com
Mongoose Studio

Contribuciones

Las contribuciones son bienvenidas. Por favor:

  1. Fork el proyecto
  2. Crea una rama para tu feature (git checkout -b feature/amazing-feature)
  3. Commit tus cambios (git commit -m 'Add amazing feature')
  4. Push a la rama (git push origin feature/amazing-feature)
  5. Abre un Pull Request

Phobos Framework by Mongoose Studio