Skip to content

Delete descendants uses old _lft and _rgt values for model #616

Open
@bereza-evgenij

Description

@bereza-evgenij

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions