¿ Cómo crear una migración en Laravel para actualizar de forma masiva a todas las claves foráneas de tu base de datos de ON DELETE NO ACTION a ON DELETE CASCADE ?

contáctanos ahora
14 de enero de 2021

En este post te enseñaré a sacarle provecho a Doctrine , que es una dependencia de laravel que permite gestionar a tus achivos php de migraciones. Hace poco tuve que hacer una actualización de una base datos con cierta cantidad de tablas y la verdad que con sólo verlo me cansé en pensar tener que hacer un montón de migraciones secuenciales que prácticamente seguían la misma lógica.

Primero les voy a dejar el código comentado y si quieren colocar esta migración les dejaré la versión sin comentar más abajo.

Bueno el primer paso es obviamente crear tu migración con el comando

php artisan make:migration rebuild_foreign_keys_with_on_delete_cascade_on_update_cascade

Una vez creado nuestro archivo de migración entramos a éste y creamos las funciones que pasaré a explicar dentro del código:

<?php

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class RebuildForeignKeysWithOnDeleteCascadeOnUpdateCascade extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        // Nos conectamos a la BD y llamamos a DoctrineSchemaManager para hacer la magia.
        $con= Schema::getConnection()->getDoctrineSchemaManager();
        // Aquí recuperamos a todos los nombres de las tablas en tu bd.
        $tb_names=$con->listTableNames();
        // Empezaremos a rastrear a todas las claves foráneas por cada tabla
        foreach($tb_names as $tb_name){
            /* Aquí el nombre de la tabla será usado en la función indica la documentación
            de laravel para poder modificar las columnas y demás aspectos de la tabla*/
            Schema::table($tb_name, function (Blueprint $table) use ($tb_name,$con) {
                // Aquí dentro llamaremos a todas las claves foráneas de esta tabla en concreto
                $foreignKeys=$con->listTableForeignKeys($tb_name);
                // Primero obviamente chequeamos si está vacío para evaluar si corremos o no esto.
                if(count($foreignKeys)){
                    /* Una vez que nos aseguramos que hay claves foráneas procedemos con la actualización
                     de ON DELETE NO ACTION a ON DELETE CASCADE. Empezamos a ir clave por clave*/
                    foreach($foreignKeys as $foreignKey){
                        // Recuperamos la columna local o sea la columna que guarda el id de la relación foránea
                        $local_col=$foreignKey->getLocalColumns()[0];
                        // Aquí recuperamos el nombre de nuestra tabla foránea 
                        $foreign_table=$foreignKey->getForeignTableName();
                        // Aquí obtenemos la columna de nuestra tabla foránea
                        $foreign_col=$foreignKey->getForeignColumns()[0];
                        // Aquí obtenemos el nombre actual que tiene la clave foránea
                        $fk_name=$foreignKey->getName();
                        /* Ahora procedemos a romper esta relación foránea,que es lo normal que haríamos
                        si quremos actualizar a una relación foránea de ON DELETE NO ACTION a CASCADE*/
                        $table->dropForeign($fk_name);
                        /* Una vez rota la relación la reconstruimos asumiendo el comportamiento es cascada
                       para los eventos ON DELETE y ON UPDATE de nuestra base de datos */
                        $table
                        ->foreign($local_col, $fk_name)
                        ->references($foreign_col)
                        ->on($foreign_table)
                        ->onUpdate('CASCADE')
                        ->onDelete('CASCADE');
                    }
                }
            });
            
        }
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */

    /* Para el rollback de esta migración hacemos lo mismo con la única diferencia que
    ahora vamos a devolver la relación a ON DELETE NO ACTION.*/
    public function down()
    {
        $con= Schema::getConnection()->getDoctrineSchemaManager();
        $tb_names=$con->listTableNames();
        foreach($tb_names as $tb_name){
            Schema::table($tb_name, function (Blueprint $table) use ($tb_name,$con) {
                $foreignKeys=$con->listTableForeignKeys($tb_name);
                if(count($foreignKeys)){
                    foreach($foreignKeys as $foreignKey){
                        $local_col=$foreignKey->getLocalColumns()[0];
                        $foreign_table=$foreignKey->getForeignTableName();
                        $foreign_col=$foreignKey->getForeignColumns()[0];
                        $fk_name=$foreignKey->getName();
                        $table->dropForeign($fk_name);
                        $table
                        ->foreign($local_col, $fk_name)
                        ->references($foreign_col)
                        ->on($foreign_table)
                        ->onUpdate('NO ACTION')
                        ->onDelete('NO ACTION');
                    }
                }
            });
            
        }
    }
}

Éste es el código sin comentarios para que lo puedan copiar y solucionar este problema de forma rápida.

<?php

use IlluminateSupportFacadesSchema;
use IlluminateDatabaseSchemaBlueprint;
use IlluminateDatabaseMigrationsMigration;

class RebuildForeignKeysWithOnDeleteCascadeOnUpdateCascade extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        $con= Schema::getConnection()->getDoctrineSchemaManager();
        $tb_names=$con->listTableNames();
        foreach($tb_names as $tb_name){
            Schema::table($tb_name, function (Blueprint $table) use ($tb_name,$con) {
                $foreignKeys=$con->listTableForeignKeys($tb_name);
                if(count($foreignKeys)){
                    foreach($foreignKeys as $foreignKey){
                        $local_col=$foreignKey->getLocalColumns()[0];
                        $foreign_table=$foreignKey->getForeignTableName();
                        $foreign_col=$foreignKey->getForeignColumns()[0];
                        $fk_name=$foreignKey->getName();
                        $table->dropForeign($fk_name);
                        $table
                        ->foreign($local_col, $fk_name)
                        ->references($foreign_col)
                        ->on($foreign_table)
                        ->onUpdate('CASCADE')
                        ->onDelete('CASCADE');
                    }
                }
            });
            
        }
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        $con= Schema::getConnection()->getDoctrineSchemaManager();
        $tb_names=$con->listTableNames();
        foreach($tb_names as $tb_name){
            Schema::table($tb_name, function (Blueprint $table) use ($tb_name,$con) {
                $foreignKeys=$con->listTableForeignKeys($tb_name);
                if(count($foreignKeys)){
                    foreach($foreignKeys as $foreignKey){
                        $local_col=$foreignKey->getLocalColumns()[0];
                        $foreign_table=$foreignKey->getForeignTableName();
                        $foreign_col=$foreignKey->getForeignColumns()[0];
                        $fk_name=$foreignKey->getName();
                        $table->dropForeign($fk_name);
                        $table
                        ->foreign($local_col, $fk_name)
                        ->references($foreign_col)
                        ->on($foreign_table)
                        ->onUpdate('NO ACTION')
                        ->onDelete('NO ACTION');
                    }
                }
            });
            
        }
    }
}

Espero haberles ayudado y que ahora tengan más tiempo . Si tienen alguna sugerencia, preguntas o mejoras pueden escribirme y procuraré responderles pronto.

Saludos!

Renzo Castillo

0 comentarios

Enviar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Artículos Relacionados

No se han artículos similares

Contáctanos

Ponemos a tu disposición todos nuestros canales de comunicación para que nos envíes tus preguntas, solicitudes y comentarios.