Eliminación en cascada en Laravel

Publicado por Lynn
hace 5 meses

Estoy utilizando Laravel y tengo un problema con las relaciones polimórficas y la eliminación en cascada. Tengo dos modelos: User y Comment. La relación entre ellos es polimórfica, ya que un usuario puede tener muchos comentarios y a la vez un comentario puede pertenecer a diferentes tipos de modelos.

Mi problema surge cuando intento eliminar un usuario. Quiero que, al eliminar un usuario, se eliminen todos sus comentarios relacionados. He utilizado la función "onDelete('cascade')" al definir la relación polimórfica en el modelo pero no está funcionando correctamente.

Este es el código de mi migración de la tabla "comments":

public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id();
        $table->text('content');
        $table->unsignedBigInteger('commentable_id');
        $table->string('commentable_type');
        $table->timestamps();

        $table->index(['commentable_id', 'commentable_type']);

        $table->foreign('commentable_id')
              ->references('id')
              ->on('users')
              ->onDelete('cascade');

        $table->foreign('commentable_id')
              ->references('id')
              ->on('posts')
              ->onDelete('cascade');
    });
}

Y este es el código de mi modelo "User":

class User extends Authenticatable
{
    use HasFactory;

    // ...

    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

¿Alguien sabe qué puedo estar haciendo mal? ¿Cómo puedo lograr que se eliminen automáticamente los comentarios al eliminar un usuario?

Al intentar eliminar un usuario actualmente, recibo un error como el siguiente:

Illuminate\Database\QueryException : SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`database`.`comments`, CONSTRAINT `comments_commentable_id_foreign` FOREIGN KEY (`commentable_id`) REFERENCES `users` (`id`)
Relaciones polimórficas Eliminación en cascada Problema
Respuesta de Gordon Shumway
hace 5 meses

¡Hola Lynn!

El problema con las relaciones polimórficas en Laravel es que no puedes usar foreign keys de la misma manera que con las relaciones uno a muchos "tradicionales", debido a que el campo commentable_id puede referirse a múltiples tablas (no solo a users, sino también a posts y cuantas otras entidades sean parte de esta relación).

La definición de las foreign keys en tu migración no es adecuada para una relación polimórfica. La migración intenta establecer una foreign key para commentable_id que referencia la tabla users y también posts, lo cual no es compatible con cómo funcionan las relaciones polimórficas.

Para lograr la eliminación en cascada de los comentarios cuando eliminas un usuario, tendrás que manejarlo manualmente en tu modelo User mediante eventos de modelo, específicamente el evento deleting. Aquí te dejo un fragmento de código que podrías utilizar en tu modelo User:

class User extends Authenticatable
{
    use HasFactory;

    //...

    public static function boot() {
        parent::boot();

        // Cuando se está por eliminar un usuario...
        static::deleting(function ($user) {
            // ...elimina todos sus comentarios relacionados.
            $user->comments()->delete();
        });
    }

    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

El método boot se llama al inicializar el modelo. Dentro de ese método, se está registrando un callback para el evento deleting que eliminará todos los comentarios relacionados con la instancia del usuario que se está a punto de eliminar.

Con este enfoque, no necesitarías las líneas problemáticas de las foreign keys en tu migración. Puedes quitar esas definiciones y dejar únicamente la creación de índices:

public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id();
        $table->text('content');
        $table->unsignedBigInteger('commentable_id');
        $table->string('commentable_type');
        $table->timestamps();

        // Simplemente crea los índices.
        $table->index(['commentable_id', 'commentable_type']);
    });
}

No olvides ejecutar tus migraciones de nuevo si realizas cambios en ellas.

Espero esto resuelva tu problema. ¡Suerte con tu proyecto!