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();
}
});
}
});
}
}
?>
<?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(); } }); } }); } } ?>
<?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);
}
}
?>
<?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); } } ?>
<?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.