You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel如何设计代码结构实现嵌套JSON的新增与upsert存储并保留关联ID

现有代码可优化点

  • 所有嵌套处理逻辑耦合在Template模型中,不符合「每个Model负责自身数据存储」的要求
  • 多次调用refresh()产生不必要的查询,性能损耗高
  • 子模型关联字段需要手动associate,重复代码多且容易出错
  • upsert方法未指定唯一冲突键,无法保证原有ID不被修改,也可能执行失败

优化实现方案

核心思路:把嵌套存储逻辑拆分到每个子模型,每个模型仅负责自身字段存储 + 直接下级关联的处理,上层模型不用感知下层的嵌套结构。

1. Template 模型实现

<?php
class Template extends Model {
    protected $table = 'templates';
    protected $primaryKey = 'templates_id';
    protected $guarded = [];

    // 你已配置的关联定义
    public function pages()
    {
        return $this->hasMany(TemplatesPage::class, 'templates_id', 'templates_id');
    }

    // 嵌套保存统一入口,兼容新增/更新
    public function saveNested(array $data)
    {
        // 保存自身非嵌套字段
        $this->fill(collect($data)->except('pages')->toArray())->save();

        // 组装页面upsert数据,自动注入当前模板ID
        $pageUpsertData = collect($data['pages'])->map(function ($pageItem) {
            $pageItem['templates_id'] = $this->templates_id;
            return collect($pageItem)->except('components')->toArray();
        })->toArray();

        // 页面upsert,唯一冲突键保证原有pages_id不变
        TemplatesPage::upsert($pageUpsertData, ['templates_id', 'pages_id']);

        // 拿到当前模板的所有关联页面,逐个处理下级嵌套
        $this->pages()->whereIn('pages_id', collect($data['pages'])->pluck('pages_id'))
            ->get()
            ->each(function (TemplatesPage $page, $index) use ($data) {
                $page->saveNested($data['pages'][$index]);
            });
    }
}

2. TemplatesPage 模型实现

<?php
class TemplatesPage extends Model {
    protected $table = 'templates_pages';
    protected $primaryKey = 'pages_id';
    protected $guarded = [];

    public function components()
    {
        return $this->hasMany(TemplatesComponent::class, 'pages_id', 'pages_id');
    }

    public function saveNested(array $data)
    {
        // 保存页面自身字段
        $this->fill(collect($data)->except('components')->toArray())->save();

        // 组装组件upsert数据,自动注入关联ID
        $componentUpsertData = collect($data['components'])->map(function ($componentItem) {
            $componentItem['templates_id'] = $this->templates_id;
            $componentItem['pages_id'] = $this->pages_id;
            return collect($componentItem)->except('lines')->toArray();
        })->toArray();

        // 组件upsert,保留原有components_id
        TemplatesComponent::upsert($componentUpsertData, ['pages_id', 'components_id']);

        // 处理组件下的嵌套线条
        $this->components()->whereIn('components_id', collect($data['components'])->pluck('components_id'))
            ->get()
            ->each(function (TemplatesComponent $component, $index) use ($data) {
                $component->saveNested($data['components'][$index]);
            });
    }
}

3. TemplatesComponent 模型实现

<?php
class TemplatesComponent extends Model {
    protected $table = 'templates_components';
    protected $primaryKey = 'components_id';
    protected $guarded = [];

    public function lines()
    {
        return $this->hasMany(TemplatesComponentsLine::class, 'components_id', 'components_id');
    }

    public function saveNested(array $data)
    {
        // 保存组件自身字段
        $this->fill(collect($data)->except('lines')->toArray())->save();

        // 组装线条upsert数据,自动注入关联ID
        $lineUpsertData = collect($data['lines'])->map(function ($lineItem) {
            $lineItem['templates_id'] = $this->templates_id;
            $lineItem['pages_id'] = $this->pages_id;
            $lineItem['components_id'] = $this->components_id;
            return $lineItem;
        })->toArray();

        // 线条upsert,保留原有lines_id
        TemplatesComponentsLine::upsert($lineUpsertData, ['components_id', 'lines_id']);
    }
}

4. 控制器调用示例

// 拿到请求的完整JSON数据后直接调用即可
$template = Template::findOrFail($request->input('templates_id'));
$template->saveNested($request->all());

方案优势

  • 每个模型仅负责自身和直接下级的存储逻辑,符合单一职责,后续加新的嵌套层级只需修改对应模型即可
  • 自动注入关联外键,不需要手动调用associate,避免漏传关联字段出错
  • 所有upsert都指定了唯一冲突键,保证原有ID不会被修改,完全符合需求
  • 去掉了不必要的refresh调用,大幅减少多余的数据库查询,性能更高
  • 新增和更新逻辑合并,不需要分开写两套方法,减少重复代码

内容的提问来源于stack exchange,提问作者flaxon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 04:12:00