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).
- 🎯 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)
composer require mongoose-studio/phobos-framework-database-migrationsEste 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
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');
}
};| 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 otrosEl 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);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 rawTypeAviso 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).
- 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.
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 archivoCoherente 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 → BlueprintComo 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 → PostgresCLI:
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 migracionesFidelidad 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.
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ónSame-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.
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();
}phobos_migrations: version (timestamp), batch (para rollback por lote), checksum (detecta
drift si editas una migración ya aplicada), applied_at.
- 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).
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.
- MySQL/MariaDB, PostgreSQL 12+, SQLite 3
- PHP 8.4+
Este proyecto está licenciado bajo la Licencia MIT - ver el archivo LICENSE para más detalles.
Marcel Rojas
marcelrojas16@gmail.com
Mongoose Studio
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