2019年1月

我的个人博客:逐步前行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将那个关联关系整体删除即可。

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

由于Laravel-admin只支持表单保存一对一关联数据,要想保存一对多关联数据,还得从了解它的源码入手,看有没有空子可钻。
首先,进入源码中的Form.php的store函数中看它是怎么保存表单数据的:

    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;
            }
......
}

额,所以必须使用一对一关联关系来保存数据,即将本来是一对多的关系写一个一对一的关联关系用户保存数据,比如:user模型中又一个friends一对多关联模型,但是为了在表单中保存一个friends数据,创建一个friend一对一关联关系在表单中用户保存关联数据。

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

在真机调试的时候,发现安卓端websocket连接老是报错,一番查证,在 https://www.myssl.cn/tools/check-server-cert.html 中确认是服务器缺少中间证书,于是在 https://www.myssl.cn/tools/downloadchain.html 中下载了一个中间证书。
使用方法:
1、将中间证书内容追加到原证书中
如:

#cat >> my.crt
-----BEGIN CERTIFICATE-----
VVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UE
ChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz
ZXJ0cnVzdC5jb20xKzApBgNVBAMTIlVUTi1VU0VSRmlyc3QtTmV0d29yayBBcHBs
NzExMDY5NDAxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29tL3Jlc291cmNl
cy9jcHMgKGMpMDcxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZhbGlkYXRlZCAt
XCKBNQNAzybtImcaJjXQsihqkuohYcWh2QuijBgXZC+o9IUl+2SNhw6OYXSJTuuD
09VFQFAaUC41rLcU9BDh6w7xmGnZJzZ0H8jm2E9NA6s6DId7qQ+f/YdkKePRR+Dw
p9GnjmheMvhqs0DFj+tCFhHX3PK8WGrYBYG8ejsgo8uKAKTkishpOMyTs4CmTlDX
chn5QGRjpq2FlIqqlTwLdMGpkeUSZjuAFblLhTQs158Q5VHC5SH+3DvJW+g7/CpT
jBhiTnfNyD19rUmrWZ2dmic50B32BAiIO9OepmVvI8nA1TBvNFfhX75cOCk=
-----END CERTIFICATE-----
(执行ctrl+d)

2、重启服务器