Description
Problem
When deleting an entity by the deleted
event, all its child elements of all levels are deleted.
When searching for child elements, a query with a filter by _lft
in the interval [a; b]
is used, where a = _lft
, b = _rgt
of the deleted entity.
But it is not taken into account that if we ourselves delete all child elements of the entity before deleting it, the tree of elements will be recalculated in terms of the _lft
and _rgt
fields.
And the interval [a; b] should also change, since the values of _lft
and _rgt
have changed for the deleted entity, but it does not change.
Because of this, the old interval [a; b]
includes elements that ARE NOT EVEN CHILDREN for the deleted entity!
And if their ids are used in related entities as foreign keys and they do not have onDelete = cascade
, then there will be an error SQLSTATE[23503]: Foreign key violation
.
And if their id's are NOT used in related entities, then elements that are not even children of the entity being deleted will be deleted!
And deleting child elements in a package is done directly by a query to the DB. Because of this, the deleting
and deleted
events from Laravel do not work for them!
Example
Migration
Schema::create('brand_categories', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('brand_id');
$table->string('name');
$table->string('code');
$table->unsignedTinyInteger('sort')->nullable();
$table->unsignedBigInteger(NestedSet::RGT)->default(0);
$table->unsignedBigInteger(NestedSet::LFT)->default(0);
$table->unsignedBigInteger(NestedSet::PARENT_ID)->nullable();
$table->index(NestedSet::getDefaultColumns());
$table->timestamps(6);
$table->foreign('brand_id')->references('id')->on('brands');
$table->foreign(NestedSet::PARENT_ID)->references('id')->on('brand_categories');
$table->unique(['brand_id', 'name']);
});
Schema::create('brand_category_lines', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('brand_category_id');
$table->string('line');
$table->timestamps(6);
$table->foreign('brand_category_id')->references('id')->on('brand_categories');
$table->unique(['brand_category_id', 'line']);
});
Models
BrandCategory.php
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Kalnoy\Nestedset\NodeTrait;
/**
* @property int $id
* @property string $name
* @property int $parent_id
* @property int $brand_id
* @property string $code
* @property string $sort
* @property CarbonInterface|null $created_at
* @property CarbonInterface|null $updated_at
*
* @property-read Collection|BrandCategoryLine[] $lines
*/
class BrandCategory extends Model
{
use NodeTrait;
protected $table = 'brand_categories';
protected $fillable = [
'name',
'code',
'parent_id',
'brand_id',
'sort',
];
protected $casts = [
'parent_id' => 'int',
'brand_id' => 'int',
];
protected $hidden = ['_lft', '_rgt'];
public function lines(): HasMany
{
return $this->hasMany(BrandCategoryLine::class, 'brand_category_id');
}
}
BrandCategoryLine.php
use Carbon\CarbonInterface;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int $brand_category_id
* @property string $line
* @property CarbonInterface|null $created_at
* @property CarbonInterface|null $updated_at
*/
class BrandCategoryLine extends Model
{
protected $table = 'brand_category_lines';
protected $fillable = [
'brand_category_id',
'line',
];
}
Observer
use App\Domain\Categories\Models\BrandCategory;
use App\Domain\Categories\Models\BrandCategoryLine;
class BrandCategoryObserver
{
public function deleting(BrandCategory $model): void
{
$model->loadMissing(['descendants', 'lines']);
$model->descendants->each(fn (BrandCategory $brandCategory) => $brandCategory->delete());
$model->lines->each(fn (BrandCategoryLine $line) => $line->delete());
}
}