Laravel-admin 使用表单动态地保存一个关联模型(源码探究到功能实现)

胡安怡
2023-12-01

我的个人博客:逐步前行STEP

有时候我们使用Laravel-admin管理数据时,需要保存一些通过程序运算出来的数据,而不只是存储写在表单中的数据,也就是需要在保存数据前可以设置或改变数据。
比如存在这么个需求:

为了快速创建\管理一些测试数据,在Admin管理端创建一个用户账户的同时,创建相关的用户信息,用户订单、地址库等等一系列信息。

以这个需求中用户关联的数据量来说,每个数据都手动输入的话,效率太低了,所以只能自动创建。
所以实现的思路是这样的,表单中只有用户的基本信息,其它的订单、地址都是在执行store函数前自动生成的,然后关联保存即可。
先看Form.php的store函数源码:

public function store()
    {
        $data = Input::all();
        ......
    }

数据来源是request对象,所以,轻而易举地想到:

在store之前往request中塞入订单、地址的关联数据

这个数据的格式在我的另一篇博文中有详细解释:laravel-admin grid中使用switch操作一对一关联属性,但是执行提交后发现并没有成功保存,于是走一波源码调试。
首先看到更新关联模型数据的代码:

 public function store()
    {
......

        DB::transaction(function () {
            $inserts = $this->prepareInsert($this->updates);

            foreach ($inserts as $column => $value) {
                $this->model->setAttribute($column, $value);
            }
            $this->model->save();

			//在这里保更新关联模型
            $this->updateRelation($this->relations);
        });
......
    }

继续进入updateRelation方法跟踪:

protected function updateRelation($relationsData)
    {
        foreach ($relationsData as $name => $values) {

            if (!method_exists($this->model, $name)) {
                continue;
            }
            $relation = $this->model->$name();

			//在这里判定是否是一对一
            $oneToOneRelation = $relation instanceof Relations\HasOne
                || $relation instanceof Relations\MorphOne
                || $relation instanceof Relations\BelongsTo;

			//在这里做一个预处理
            $prepared = $this->prepareUpdate([$name => $values], $oneToOneRelation);
			//预处理的结果为空则没有后续处理
            if (empty($prepared)) {
                continue;
            }

经过打断点调试,发现我在store之前插入的订单、地址关联数据并没有通过预处理,所以再来看看prepareUpdate预处理是什么鬼:

protected function prepareUpdate(array $updates, $oneToOneRelation = false)
    {
        $prepared = [];

        /** @var Field $field */
        //$this->builder->fields() 就是表单字段相关属性
        foreach ($this->builder->fields() as $field) {
            $columns = $field->column();//这个是字段名称了

            // If column not in input array data, then continue.
            if (!array_has($updates, $columns)) {//关键在这,如果表单字段不在request的数据中就过滤掉
                continue;
            }

      ......

    }

在上面的注释中,清楚地表明了一个状况:

form表单的提交保存只能保存表单中有的字段,不然都会被过滤掉。

所以,我们需要让Laravel-admin认为我们表单中有订单、地址关联字段,根据我们的需求,很容易想到使用hidden组件:

只要把所有关联关系需要更新的字段都使用hidden列出来即可,不需要赋值

而且在store前,如果根据某些条件不需要保存这个关联关系的话,直接使用request的offsetUnset将那个关联关系整体删除即可。

 类似资料: