issue: 建议db json增加多个对象更新
1 2 3 4 5 6 7 8 9
| public function index () { Banner::where('id', 1) ->update([ 'info->name' => 'hongfs', ]);
return json([]); }
|
单条是可以正常执行的。
1
| [SQL] UPDATE `fa_banner` SET `info` = json_set(ifnull(`info`, '{}'), '$.name', 'hongfs') WHERE `id` = 1 [ RunTime:0.036457s ]
|
1 2 3 4 5 6 7 8 9 10
| public function index () { Banner::where('id', 1) ->update([ 'info->name' => 'hongfs', 'info->hi' => 'hongfs', ]);
return json([]); }
|

两条记录我们就收获了一个报错。
1
| UPDATE `fa_banner` SET `info` = json_set(ifnull(`info`, '{}'), '$.hi', 'hongfs') WHERE `id` = 1
|
可以看到字段就只剩下最后一个了。
目前优化的思路还是先支持多个,通过全局搜索 json_set(
发现只存在于 vendor/topthink/think-orm/src/db/Builder.php
。
通过查阅 Mysql JSON_SET 文档,是可以一次性更新多个值的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array { foreach ($data as $key => $val) { if (false !== strpos($key, '->')) { [$key, $name] = explode('->', $key, 2); $item = $this->parseKey($query, $key);
if(isset($result[$item]) && strpos($result[$item], 'json_set(') === 0) { $result[$item] = substr($result[$item], 0, -1) . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')'; } else { $result[$item] = 'json_set(ifnull(' . $item . ', \'{}\'), \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')'; } } } }
|
可以看见现在 SQL 语句是正常的了,程序也没报错了。
1
| UPDATE `fa_banner` SET `info` = json_set(ifnull(`info`, '{}'), '$.name', 'hongfs', '$.hi', 'hongfs') WHERE `id` = 1
|
还有另外一个场景,新增/更新和覆盖同时存在。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public function index () { Banner::where('id', 1) ->json(['info']) ->update([ 'info->name' => 'hongfs', 'info->hi' => 'hongfs', 'info' => [ 'hongfs' => 'hongfs', ], ]);
return json([]); }
|

如果你执行,现在还是会报那个错误。
如果不预先使用 json()
把字段添加进去,那会收到一个 “未定义数组下标: 0” 的错误。
这时我看了下报错的语句,发现只剩下后半段了。

通过使用 Xdebug 发现被截断的位置是 vendor/topthink/think-orm/src/db/PDOConnection.php:getRealSql
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public function getRealSql(string $sql, array $bind = []): string { foreach ($bind as $key => $val) { $value = strval(is_array($val) ? $val[0] : $val); $type = is_array($val) ? $val[1] : PDO::PARAM_STR;
if (self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) { $value = '\'' . addslashes($value) . '\''; } elseif (PDO::PARAM_INT == $type && '' === $value) { $value = '0'; }
$sql = is_numeric($key) ? substr_replace($sql, $value, strpos($sql, '?'), 1) : substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); }
return rtrim($sql); }
|
循环绑定参数然后进行真实数据赋值,但是我们会存在覆盖行为,一些参数再也用不到,但这里还是进行循环的。所以我们可以在最前面加个判断,如果参数标识值不存在则可以跳过。
1 2 3 4 5 6 7 8
| public function getRealSql(string $sql, array $bind = []): string { foreach ($bind as $key => $val) { if(strpos($sql, ':' . $key) === false) { continue; } } }
|
执行后 SQL 是正常的,但还是之前的那个错误。


我注意到了具体的报错信息,通过调用链路的信息结合报错内容,我开始猜测是不是绑定参数有问题。我又回到了 parseData
,如果被覆盖了,那应该清除原本这个字段的绑定参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array { foreach ($data as $key => $val) { if() { } elseif (is_scalar($val)) {
if(isset($result[$item]) && !$data instanceof Raw && in_array($key, $options['json'])) { $binds = array_filter($query->getBind(false), function ($bind_key) use($result, $item) { return strpos($result[$item], ':' . $bind_key) === false; }, ARRAY_FILTER_USE_KEY);
$query->getBind();
foreach ($binds as $bind_key => $bind_value) { $query->bindValue($bind_value[0], $bind_value[1], $bind_key); } }
$result[$item] = $this->parseDataBind($query, $key, $val, $bind); } } }
|
这时我们的语句就正常了。
1
| UPDATE `fa_banner` SET `info` = '{\"hongfs\":\"hongfs\"}' WHERE `id` = 1
|
到这里会发现其实 getRealSql
那一步就没必要了的。
最后,如果是这种呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public function index () { Banner::where('id', 1) ->json(['info']) ->update([ 'info' => [ 'hongfs' => 'hongfs', ], 'info->name' => 'hongfs', 'info->hi' => 'hongfs', ]);
return json([]); }
|