ThinkPHP6 ORM append 优化

国庆刷 GitHub 发现有人在 issue 提了一个 append 的操作,可以直接减少输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function index ()
{
$banner = Banner::field('id, cid, images, href')
->where('status', 1)
->find();

$data = $banner->append([
'category' => [
'name',
],
])->toArray();

return json($data);
}
1
2
3
4
5
6
7
8
9
10
11
{
"id": 1,
"cid": 1,
"images": "https://cdn.hongfs.cn/tmp/douyin_20211015/dynamic_cover/184.gif",
"href": "https://www.hongfs.cn/?f=mini_banner_top_1",
"category": {
"id": 1,
"name": "首页-顶部",
"create_at": "2022-10-04 16:32:19"
}
}

可以看见目前因为有 bug,所以 category 这个一对一关联是直接显示全部数据的。

对代码进行跟踪后,append 方法添加的数据最终会在 toArray 完成处理。

vendor/topthink/think-orm/src/model/concern/Conversion.php

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 数据输出需要追加的属性
* @var array
*/
protected $append = [];

/**
* 设置需要附加的输出属性
* @access public
* @param array $append 属性列表
* @param bool $merge 是否合并
* @return $this
*/
public function append(array $append = [], bool $merge = false)
{
if ($merge) {
$this->append = array_merge($this->append, $append);
} else {
$this->append = $append;
}

return $this;
}

/**
* 转换当前模型对象为数组
* @access public
* @return array
*/
public function toArray(): array
{
...
// 追加属性(必须定义获取器)
foreach ($this->append as $key => $name) {
$this->appendAttrToArray($item, $key, $name);
}
...
}

protected function appendAttrToArray(array &$item, $key, $name)
{
if (is_array($name)) {
// 追加关联对象属性
$relation = $this->getRelation($key, true);
$item[$key] = $relation ? $relation->append($name)
->toArray() : [];
} elseif (strpos($name, '.')) {
[$key, $attr] = explode('.', $name);
// 追加关联对象属性
$relation = $this->getRelation($key, true);
$item[$key] = $relation ? $relation->append([$attr])
->toArray() : [];
} else {
$value = $this->getAttr($name);
$item[$name] = $value;

$this->getBindAttrValue($name, $value, $item);
}
}

$this->append 循环后给到 appendAttrToArray 处理。 appendAttrToArray 的入参应该是:

1
$this->appendAttrToArray(&$item, 'category', ['name']);

所以我们会进入第一个 if 的判断。因为之前看过一部分源码,所以直接就能知道问题了,$relation->append($name) 这里变成了追加数据表不存在的额外字段了。

最方便就是通过循环然后一个一个把我们需要的字段添加上去。

1
2
3
4
5
6
7
8
if(is_array($name)) {
// 追加关联对象属性
$relation = $this->getRelation($key, true);

foreach ($name as $attr) {
$item[$key][$attr] = $relation->getAttr($attr);
}
}
1
2
3
4
5
6
7
8
9
{
"id": 1,
"cid": 1,
"images": "https://cdn.hongfs.cn/tmp/douyin_20211015/dynamic_cover/184.gif",
"href": "https://www.hongfs.cn/?f=mini_banner_top_1",
"category": {
"name": "首页-顶部"
}
}

接着我们要研究下中间隔着 . 的方式。

1
2
3
4
5
6
7
8
9
10
11
12
public function index ()
{
$banner = Banner::field('id, cid, images, href')
->where('status', 1)
->find();

$data = $banner->append([
'category.name',
])->toArray();

return json($data);
}

和上面的处理方式是差不多的。要注意应该只有一个 .,不然会出问题的。

1
2
3
4
5
6
7
if(strpos($name, '.')) {
[$key, $attr] = explode('.', $name);
// 追加关联对象属性
$relation = $this->getRelation($key, true);

$item[$key][$attr] = $relation->getAttr($attr);
}
1
2
3
4
5
6
7
8
9
{
"id": 1,
"cid": 1,
"images": "https://cdn.hongfs.cn/tmp/douyin_20211015/dynamic_cover/184.gif",
"href": "https://www.hongfs.cn/?f=mini_banner_top_1",
"category": {
"name": "首页-顶部"
}
}

接着在使用过程中还会遇到其他一些问题。

比如点的,如果我的数量有多少条,就有多少条记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function index ()
{
$banner = Banner::field('id, cid, images, href')
->where('status', 1)
->find();

$data = $banner->append([
'category.id',
'category.name',
'category.create_at',
])->toArray();

return json($data);
}
1
2
3
[SQL] SELECT * FROM `fa_banner_category` WHERE  `id` = 1 LIMIT 1 [ RunTime:0.046582s ]
[SQL] SELECT * FROM `fa_banner_category` WHERE `id` = 1 LIMIT 1 [ RunTime:0.046457s ]
[SQL] SELECT * FROM `fa_banner_category` WHERE `id` = 1 LIMIT 1 [ RunTime:0.043727s ]

我想是不是在 append 方法优化下会比较好呢,把 . 的进行拆分以便可以进第一个 if 判断。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 设置需要附加的输出属性
* @access public
* @param array $append 属性列表
* @param bool $merge 是否合并
* @return $this
*/
public function append(array $append = [], bool $merge = false)
{
$tmp = $merge ? $this->append : [];

foreach($append as $k => $name) {
if(is_array($name)) {
if(!isset($tmp[$k])) {
$tmp[$k] = $name;
} else {
$tmp[$k] = array_merge($tmp[$k], $name);
}

$index = array_search($k, $tmp);

if($index !== false) {
array_splice($tmp, $index, 1);
}

continue;
} elseif(strpos($name, '.')) {
[$key, $attr] = explode('.', $name);

if(!isset($tmp[$key])) {
$tmp[$key] = [];
}

if(in_array($attr, $tmp[$key])) {
continue;
}

$tmp[$key][] = $attr;
} else {
if(in_array($name, $tmp)) {
continue;
} else if(isset($tmp[$name])) {
continue;
}

$tmp[] = $name;
}
}

$append = $tmp;

return $this;
}
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
28
29
30
public function index ()
{
$banner = Banner::field('id, cid, images, href')
->where('status', 1)
->find();

$data = $banner->append([
'category.id',
'category.name',
'category.create_at',
])->toArray();

// 转换前
// $this->append = [
// 'category.id',
// 'category.name',
// 'category.create_at',
// ];

// 转换后
// $this->append = [
// 'category' => [
// 'id',
// 'name',
// 'create_at',
// ],
// ];

return json($data);
}

现在我们的 SQL 就又回归到了一条。

下面这种情况肯定是不生效的,比较你早已经把关联进行引入了,这时你应该用 hidden 隐藏掉不想的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function index ()
{
$banner = Banner::field('id, cid, images, href')
->where('status', 1)
->find();

$banner->category;

$data = $banner->append([
'category' => [
'name',
],
])->toArray();

return json($data);
}

目前看,append 适合那种不需要额外 where 的,当然你也可以写在关联后面。append 查询的语句目前是 SELECT * 暂时不能直接改,如果有需要限制具体字段还是 field。减少无关字段的返回可以提升网络速度,业务规模小就没必要了。

往上