Laravel: Exclusão em cascata usando soft delete (Cascade Delete)

Um dos recursos mais interessantes do Laravel é a possibilidade de implementação de “soft delete” com extrema facilidade nos sistemas, só que com isso surgem alguns problemas.

Para manter a integridade dos relacionamentos na base de dados, a utilização de “foreign keys” não é suficiente quando se usa “sof delete”, e seria necessário a criação de inúmeras “triggers” para executar as ações em cascata originadas pela “exclusão” de um registro, “exclusão” entre aspas porque usando “soft delete” nenhum registro é excluído fisicamente. Mas mesmo implementando o “soft delete” por padrão no sistema, haverão casos de tabelas que não será necessário ficar mantendo um histórico de registros, e que uma exclusão física seria recomendado para não ficar sobrecarregando o banco de dados com registros desnecessários, principalmente se uma rotina de backups é executada.

Foi então que após pesquisar bastante, e ver inumeras sugestões, como reescrever funções nativas do framework e tantas outras absurdas, que resolvi criar minha própria solução.

Vamos lá.

Todos os modelos em um projeto padrão utilizando Laravel estendem do ORM Eloquent, então a primeira coisa é criar um modelo base, de onde todos os outros modelos do sistema passarão a estender.

<?php

namespace App;

use Eloquent;
use Illuminate\Database\Eloquent\SoftDeletes;

/**
 * Class Model
 * @package App
 */
class Model extends Eloquent
{
    use SoftDeletes;

    /**
     * Enable Table Timestamps
     *
     * @var bool
     */
    public $timestamps = true;

    /**
     * Execute Actions On Boot
     *
     * @return void
     */
    public static function boot()
    {
    	parent::boot();

    	static::deleting(function($model) {

            // CASCADE SOFT DELETE

            if (isset($model->cascadeDelete)) {

                collect($model->cascadeDelete)->each(function ($class, $method) use ($model) {

                    if (method_exists($model, $method)) {

                        collect($model->$method($class)->get())->each(function ($model) {
                            $model->delete();
                        });

                        $model->$method($class)->delete();
                    }
                });
            }

            // CASCADE PERMANENTLY DELETE

            if (isset($model->cascadeForceDelete)) {

                collect($model->cascadeForceDelete)->each(function ($class, $method) use ($model) {

                    if (method_exists($model, $method)) {

                        collect($model->$method($class)->get())->each(function ($model) {
                            $model->forceDelete();
                        });

                        $model->$method($class)->forceDelete();
                    }
                });
            }
        });
    }
}

?>

Agora, todo modelo que estender de “Model”, vai usar “soft delete” por padrão, e para que uma exclusão em cascata ocorra, ou seja, todos os relacionamentos que por ventura devam ser excluídos também sejam, você precisa definir a lista de métodos e classes que devem ser excluídos.

<?php

namespace App;

use App\Model;

/**
 * Class Example
 * @package App
 */
class Example extends Model
{
    /**
     * Table name
     *
     * @var string
     */
    public $table = 'example';

    /**
     * Relations that should be soft deleted
     *
     * @var array
     */
    public $cascadeDelete = [
        'hasMany' => \App\Relation::class,
    ];

    /**
     * Relations that should be permanently deleted
     *
     * @var array
     */
    public $cascadeForceDelete = [
        'hasMany' => \App\Relation::class,
    ];

    /**
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     **/
    public function Relations()
    {
        return $this->hasMany(\App\Relation::class);
    }
}

?>

Essa solução tem funcionado muito bem até então, e tem sido adotada como padrão em todos os sistemas que desenvolvo usando Laravel.

Espero ter ajudado.