Migraciones de base de datos en PHP con Phinx: crear, ejecutar y revertir cambios de esquema

Phinx es una librería de migraciones de base de datos para PHP que funciona de forma independiente a cualquier framework. Con ella defines los cambios de esquema en clases PHP versionadas, y puedes aplicarlos o revertirlos en cualquier momento.

Instalación

composer require robmorgan/phinx

Configuración

Crea phinx.php en la raíz del proyecto:

<?php
return [
    'paths' => [
        'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
        'seeds'      => '%%PHINX_CONFIG_DIR%%/db/seeds',
    ],
    'environments' => [
        'default_migration_table' => 'phinxlog',
        'default_environment'     => 'development',
        'development' => [
            'adapter' => 'mysql',
            'host'    => 'localhost',
            'name'    => 'mi_proyecto',
            'user'    => 'root',
            'pass'    => 'secret',
            'charset' => 'utf8mb4',
        ],
        'production' => [
            'adapter' => 'mysql',
            'host'    => getenv('DB_HOST'),
            'name'    => getenv('DB_NAME'),
            'user'    => getenv('DB_USER'),
            'pass'    => getenv('DB_PASS'),
            'charset' => 'utf8mb4',
        ],
    ],
    'version_order' => 'creation',
];
?>

Crear una migración

vendor/bin/phinx create CrearTablaProductos

Se genera un fichero como db/migrations/20240601120000_crear_tabla_productos.php:

<?php
use PhinxMigrationAbstractMigration;

class CrearTablaProductos extends AbstractMigration
{
    public function change(): void
    {
        $tabla = $this->table('productos');
        $tabla
            ->addColumn('nombre',       'string',  ['limit' => 200])
            ->addColumn('precio',       'decimal', ['precision' => 10, 'scale' => 2])
            ->addColumn('stock',        'integer', ['default' => 0])
            ->addColumn('categoria_id', 'integer')
            ->addColumn('created_at',   'datetime', ['null' => true])
            ->addColumn('updated_at',   'datetime', ['null' => true])
            ->create();
    }
}
?>

El método change() es reversible: Phinx sabe cómo deshacer la creación de tabla automáticamente.

Añadir índices únicos y claves foráneas

<?php
class AnadirIndicesProductos extends AbstractMigration
{
    public function change(): void
    {
        $tabla = $this->table('productos');

        // Índice único en nombre
        $tabla->addIndex(['nombre'], ['unique' => true, 'name' => 'ux_productos_nombre']);

        // Clave foránea a categorias
        $tabla->addForeignKey(
            'categoria_id',
            'categorias',
            'id',
            ['delete' => 'CASCADE', 'update' => 'NO_ACTION']
        );

        $tabla->update();
    }
}
?>

Ejecutar y revertir migraciones

# Aplica todas las migraciones pendientes
vendor/bin/phinx migrate

# Aplica solo hasta una versión específica
vendor/bin/phinx migrate -t 20240601120000

# Revertir la última migración
vendor/bin/phinx rollback

# Revertir hasta un punto
vendor/bin/phinx rollback -t 20240601120000

# Ver el estado de las migraciones
vendor/bin/phinx status

Migraciones con up() y down() explícitos

Cuando el cambio no es automáticamente reversible (por ejemplo, modificar datos), usa up() y down() por separado:

<?php
class NormalizarEmailsUsuarios extends AbstractMigration
{
    public function up(): void
    {
        $this->execute("UPDATE usuarios SET email = LOWER(email)");
    }

    public function down(): void
    {
        // No se puede deshacer una normalización de datos
        // Dejamos vacío o lanzamos excepción
        $this->output->writeln('Rollback no aplicable para esta migración.');
    }
}
?>

Seeds: poblar la base de datos

vendor/bin/phinx seed:create ProductosSeeder
<?php
use PhinxSeedAbstractSeed;

class ProductosSeeder extends AbstractSeed
{
    public function run(): void
    {
        $datos = [
            ['nombre' => 'Teclado',  'precio' => 49.99, 'stock' => 10, 'categoria_id' => 1],
            ['nombre' => 'Ratón',    'precio' => 29.99, 'stock' => 25, 'categoria_id' => 1],
            ['nombre' => 'Monitor',  'precio' => 299.00,'stock' => 5,  'categoria_id' => 2],
        ];

        $this->table('productos')->insert($datos)->saveData();
    }
}
?>
# Ejecutar todos los seeds
vendor/bin/phinx seed:run

# Ejecutar un seed específico
vendor/bin/phinx seed:run -s ProductosSeeder

Integración con entornos de producción

# Apuntar a producción
vendor/bin/phinx migrate -e production

Errores comunes

  • change() con operaciones no reversibles: si añades dropColumn() dentro de change(), el rollback fallará. Usa up()/down() explícitos.
  • Claves foráneas sin índice previo: MySQL requiere que la columna referenciada tenga un índice. Phinx crea el índice automáticamente, pero si la tabla referenciada no existe todavía, ordena bien las migraciones.
  • Fichero phinxlog corrupto: si truncas la tabla phinxlog a mano, Phinx perderá el rastro de qué migraciones están aplicadas. Nunca la toques directamente.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP