mongoose-studio / phobos-framework-database-migrations
Phobos Framework — Migraciones y seeders con el DSL Harmonia, agnóstico multi-motor (MySQL, PostgreSQL, SQLite)
Package info
github.com/mongoose-studio/phobos-framework-database-migrations
pkg:composer/mongoose-studio/phobos-framework-database-migrations
Requires
- php: >=8.4
- mongoose-studio/phobos-framework-database: ^3.2
Requires (Dev)
README
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
Blueprintfluido compila a MySQL, PostgreSQL y SQLite - 🧬 Convenciones Phobos -
uuidv7()(calza conkeyStrategy = uuidv7) yauditColumns()en una línea - 🔀 Escotilla híbrida -
rawType()ySchema::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/upserttraducidos por motor - 🧾 Dry-run / SQL plano - Exporta el DDL sin ejecutar nada, incluso 100% offline
- 🗂️ Multi-tenant - El
Migratorrecibe la conexión; aplicar a N tenants es recorrer conexiones - 🧮 Tabla de control -
phobos_migrationsconbatch(rollback por lote) ychecksum(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,-postgresy/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 atrue/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:
ALTERes 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:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/amazing-feature) - Commit tus cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
Phobos Framework by Mongoose Studio