¿Cómo eliminar registros relacionados cuando se elimina un registro principal en Laravel?

6 minutos de lectura

Tengo esta tabla de facturas que tiene la siguiente estructura

id | name | amount | deleted_at
2    iMac   1500   | NULL

y una tabla de pagos con la siguiente estructura

id | invoice_id | amount | deleted_at
2    2            1000   | NULL

Modelo de factura

class Invoice extends Model {

    use SoftDeletes;

}

aquí está el código para eliminar la factura

public function cance(Request $request,$id)
{
    $record = Invoice::findOrFail($id);
    $record->delete();
    return response()->json([
        'success' => 'OK',
    ]);
}

Modelo de pagos

class Payment extends Model {

    use SoftDeletes;

}

La tabla softDelete on Invoice funciona perfectamente, pero sus registros relacionados (pagos) todavía existen. ¿Cómo los elimino usando softDelete?

Avatar de usuario de jedrzej.kurylo
jedrzej.kurylo

Elocuente no proporciona la eliminación automática de objetos relacionados, por lo tanto, deberá escribir algo de código usted mismo. Afortunadamente, es bastante simple.

modelos elocuentes disparar diferentes eventos en diferentes etapas del ciclo de vida del modelo, como crear, crear, eliminar, eliminar, etc. Puede leer más sobre esto aquí: http://laravel.com/docs/5.1/eloquent#events. Lo que necesita es un oyente que se ejecutará cuando eliminado se activa el evento: este detector debe eliminar todos los objetos relacionados.

Puede registrar oyentes modelo en su modelo bota() método. El oyente debe recorrer todos los pagos de la factura que se está eliminando y debe eliminarlos uno por uno. La eliminación masiva no funcionará aquí, ya que ejecutaría la consulta SQL directamente sin pasar por los eventos del modelo.

Esto hará el truco:

class MyModel extends Model {
  protected static function boot() {
    parent::boot();

    static::deleted(function ($invoice) {
      $invoice->payments()->delete();
    });
  }
}

  • ¡No funciona! FatalErrorException en Invoice.php línea 18: No se puede hacer que el método estático Illuminate\Database\Eloquent\Model::boot() no sea estático en la clase App\Models\Invoice

    – usuario3407278

    23 de agosto de 2015 a las 7:07

  • Corregido, a la función le faltaba el modificador estático

    – jedrzej.kurylo

    23 de agosto de 2015 a las 7:10

  • ¡Eso funcionó maravillosamente! ¡Muchas gracias! ¿Esto causa algún problema de rendimiento cuando se eliminan suavemente unos 100 registros?

    – usuario3407278

    23 de agosto de 2015 a las 7:18

  • Tienes que buscarlos todos y luego guardarlos, por lo que son 101 consultas adicionales… En tal caso, podrías configurar delete_at manualmente para modelos relacionados, esto será menos limpio pero ejecutará solo una consulta SQL. Actualizaré la respuesta en un segundo.

    – jedrzej.kurylo

    23 de agosto de 2015 a las 7:20

  • Sé que esto es antiguo, pero realmente recomendaría el uso de observadores para lograrlo.

    – stetim94

    11 de noviembre de 2019 a las 14:01

Avatar de usuario de Rwd
Rwd

Puedes ir de 2 maneras con esto.

La forma más sencilla sería anular Eloquents delete() método e incluir los modelos relacionados también, por ejemplo:

public function delete()
{
    $this->payments()->delete();
    return parent::delete();
} 

El método anterior debería funcionar, pero parece un poco sucio y diría que no es el método preferido dentro de la comunidad.

La forma más limpia (IMO) sería aprovechar los eventos de Eloquents, por ejemplo:

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

    static::deleting(function($invoice) { 
         $invoice->payments()->delete();

    });
}

Cualquiera (pero no ambos) de los métodos anteriores entraría en su Invoice modelo. Además, asumo que tiene sus relaciones configuradas en su modelo, sin embargo, no estoy seguro si permite pagos múltiples para una factura. De cualquier manera, es posible que deba cambiar el payments() en los ejemplos a lo que sea que haya llamado la relación en su modelo de factura.

¡Espero que esto ayude!

  • Llamar directamente a delete() en la relación de pagos omitirá el modelo y no activará SoftDelete en los modelos relacionados. Se eliminarán de la base de datos. Para hacer que thek se elimine temporalmente, debe llamar a delete () en cada uno de los modelos relacionados.

    – jedrzej.kurylo

    23 de agosto de 2015 a las 6:38

  • También le falta la declaración de devolución en su método de eliminación anulado: debería hacer “return parent::delete();”, de lo contrario, perderá el valor que se devolvería de delete() si no lo hubiera sobrescrito.

    – jedrzej.kurylo

    23 de agosto de 2015 a las 6:54

  • @ jedrzej.kurylo, acabo de probar para asegurarme de que SÍ puedes usar la eliminación temporal en una relación.

    – Rwd

    23 de agosto de 2015 a las 7:30

  • Cierto, acabo de comprobar el código. Es bueno saberlo para el futuro 🙂

    – jedrzej.kurylo

    23 de agosto de 2015 a las 7:43

  • ¿garantiza la transacción @RossWilson?

    –Tomonso Ejang

    20 de julio de 2018 a las 8:07


Avatar de usuario de Salar Bahador
salar bahador

Sé que hiciste esta pregunta hace mucho tiempo, pero encontré este paquete ser muy simple y directo.

O puedes usar este paquete también es útil.

Recuerda para instalar la versión correcta dependiendo de su versión de laravel.

Debes instalarlo a través de composer:

 composer require askedio/laravel5-soft-cascade ^version

En segundo paquete:

 composer require iatstuti/laravel-cascade-soft-deletes

Registre el proveedor de servicios en su config/app.php.

puede leer los documentos en la página de GitHub.

Si elimina un registro, este paquete reconoce todos sus elementos secundarios y también los elimina temporalmente.

Si tiene otra relación en su modelo hijo, use su rasgo en ese modelo también. es mucho más fácil que hacerlo manualmente.

El segundo paquete tiene la ventaja de eliminar a los nietos del modelo. en algunos casos, digo que es un mejor enfoque.

  • Usé el segundo paquete y funcionó muy bien para eliminar (), pero ¿puedo hacer que funcione también para restaurar?

    – hassanrazadev

    26 de marzo de 2020 a las 13:03

Si la relación de su base de datos no va más allá de una sola capa, entonces simplemente podría usar eventos de Laravel para manejar sus eliminaciones suaves dentro del Modelo boot() método de la siguiente manera:

<?php
//...

    protected static boot() {
        parent::boot();

        static::deleting(function($invoice) { 
             $invoice->payments()->delete();

        }); 
    }

Sin embargo, si su estructura es más profunda que una sola capatendrá que modificar ese fragmento de código.

Digamos, por ejemplo, que no desea eliminar los pagos de una factura, sino todo el historial de pagos de un usuario determinado.

<?php

// ...

class Invoice extends Model
{
    // ...

    /**
     * Holds the methods names of Eloquent Relations
     * to fall on delete cascade or on restoring
     * 
     * @var array
     */
    protected static $relations_to_cascade = ['payments']; 

    protected static boot()
    {
        parent::boot();

        static::deleting(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->delete();
                }
            }
        });

        static::restoring(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->withTrashed()->restore();
                }
            }
        });
    }

    public function payments()
    {
        return $this->hasMany(Payment::class);
    }
}

<?php

// ...

class User extends Model
{
    // ...

    /**
     * Holds the methods names of Eloquent Relations 
     * to fall on delete cascade or on restoring
     * 
     * @var array
     */
    protected static $relations_to_cascade = ['invoices']; 

    protected static boot()
    {
        parent::boot();

        static::deleting(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->delete();
                }
            }
        });

        static::restoring(function($resource) {
            foreach (static::$relations_to_cascade as $relation) {
                foreach ($resource->{$relation}()->get() as $item) {
                    $item->withTrashed()->restore();
                }
            }
        });
    }

    public function invoices()
    {
        return $this->hasMany(Invoice::class);
    }
}

Este paradigma asegura que Laravel siga la madriguera del conejo sin importar qué tan profundo sea.

También puede utilizar Observadores de modelos:

php artisan make:Observer InvoiceOberser --model=Invoice

Creará un nuevo archivo en /app/Observers/InvoiceObserver.php con los siguientes métodos:

  • creado
  • actualizado
  • eliminado
  • restaurado
  • forzadoEliminado

Solo necesita actualizar el método eliminado a esto:

public function deleted(Invoice $invoice)
{
    $invoice->payments()->delete();
}

Y finalmente en /app/Providers/EventServiceProvider.php agregue estas líneas:

// On the top
use App\Models\Invoice;
use App\Observers\InvoiceObserver;

// On boot method
Invoice::observe(InvoiceObserver::class);

¿Ha sido útil esta solución?