Laravel删除事件在递归树删除中显示错误的子节点标题

后端开发 2026-07-10

我有一个自引用的Laravel模型用于分类树。每个节点可以有子节点,我想删除一个节点及其所有后代。

我的关系是这样的:

public function children()
{
    return $this->hasMany(self::class, 'parent_id')->orderBy('sort');
}

我还定义了一个获取器,用来构建完整的树路径:

// Full title
public function getFitleAttribute()
{
    return implode(' > ', array_map(function ($category) {
        return $category->title;
    }, $this->getTreePath()));
}

以及递归路径方法是:

public function getTreePath($model = null)
{
    $path = [];

    if ($model === null) {
        $model = $this;
    }

    $path = $this->getTreePathNode($model);

    return array_reverse($path);
}

public function getTreePathNode($model)
{
    $path[] = $model;

    if ($model->parent) {
        $path = array_merge($path, $this->getTreePathNode($model->parent));
    }

    return $path;
}

当我在控制器中删除模型时,结果符合预期:

public function destroy(Category $category)
{

    foreach ($category->children as $child) {
        Log::info('deleting child title: ' . $child->fitle); // Print correct children title
        Category::deleteWithChildren($child);
    }

    $category->delete();

    return redirect(route('category.index'))
        ->with('status', __('Category deleted'));
}
protected static function deleteWithChildren($category)
{
    foreach ($category->children as $child) {
        self::deleteWithChildren($child);
    }
     $category->delete();
}

日志打印出正确的子节点标题。

然而,当我将递归删除移到模型的 deleting 事件时,日志显示了错误的值。它打印的是同级节点的标题,而不是实际子节点的标题:

public static function booted(): void
{
    static::deleting(function ($category) {

        Log::info('deleted title: ' . $category->fitle);

        foreach ($category->children as $child) {
            Log::info('deleting child title: ' . $child->fitle);
            static::deleteWithChildren($child);
        }
    });
}

我期望在 deleting 事件中的 $category->children 始终包含当前模型的直接子节点。

为什么在 deleting 事件中,$category->children 的行为会不同?在Laravel中递归删除自引用树的正确方法是什么,以避免得到错误的子数据?

环境:Laravel框架10.48.22

解决方案

更新你模型的 booted 方法,并移除手动的 deleteWithChildren 辅助函数:

protected static function booted(): void
{
    static::deleting(function ($category) {
        // 1. Log the current category being deleted
        Log::info('Deleting category: ' . $category->fitle);

        // 2. Trigger delete on children. 
        // Use children()->get() to ensure a fresh query/collection 
        // for this specific deletion cycle.
        $category->children()->get()->each(function ($child) {
            $child->delete(); // This triggers the 'deleting' event for the child
        });
    });
}

你的控制器只需要调用一个单独的 delete()

public function destroy(Category $category)
{
    // The event takes care of everything recursively
    $category->delete();

    return redirect(route('category.index'))
        ->with('status', __('Category deleted'));
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章