ThinkPHP8 ORM 下的 append/hidden 在某类场景下使用失效

issue

我们先复现这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace app\controller;

use app\BaseController;
use app\model\Order;
use app\Request;

class Index extends BaseController
{
public function index(Request $request)
{
$data = Order::where('orderNumber', 10100)
->where('status', 'Shipped')
->select()
->hidden([
'details' => ['orderNumber'],
'hongfs',
])
->toArray();

return json($data);
}
}
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
<?php
declare (strict_types = 1);

namespace app\model;

use think\Model;

/**
* @mixin \think\Model
*/
class Order extends Model
{
protected $name = 'orders';

protected $append = [
'details',
'hongfs',
];

public function getHongfsAttr()
{
return 'china';
}

public function details()
{
return $this->hasMany(OrderDetails::class, 'orderNumber', 'orderNumber');
}
}

请求看看返回。

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
[
{
"details": [
{
"productCode": "S18_1749",
"quantityOrdered": 30,
"priceEach": "136.00",
"orderLineNumber": 3
},
{
"productCode": "S18_2248",
"quantityOrdered": 50,
"priceEach": "55.09",
"orderLineNumber": 2
},
{
"productCode": "S18_4409",
"quantityOrdered": 22,
"priceEach": "75.46",
"orderLineNumber": 4
},
{
"productCode": "S24_1937",
"quantityOrdered": 49,
"priceEach": "35.29",
"orderLineNumber": 1
}
],
"hongfs": "china",
"orderNumber": 10100,
"orderDate": "2003-01-06",
"requiredDate": "2003-01-13",
"shippedDate": "2003-01-10",
"status": "Shipped",
"comments": null,
"customerNumber": 363,
"delete_time": 0
}
]

orderNumber 的隐藏是生效了,但 hongfs 是没有的。

那我们直接去看 toArray 的源码。(select 是查询,不干这事,你语义化去理解代码可以大大提升排查速度) 这块 IDEA 编辑器没办法找到相关路径,因为我们是模型的查询,所以涉及集合问题就要去 think-orm 这里看,如果后期我们使用了 collect 那就看 think-helper ,现在我们追踪到的路径是 vendor/topthink/think-orm/src/model/concern/Conversion.php,然后找到 toArray 函数的代码。

1
2
3
4
5
6
7
8
9
10
// 追加属性(必须定义获取器)
foreach ($this->append as $key => $name) {
$this->appendAttrToArray($item, $key, $name, $visible, $hidden);
}

// 合并关联数据
$data = array_merge($this->data, $this->relation);

foreach ($data as $key => $val) {
}

这里是分两块的,一个是追加属性,一个是处理模型原本数据。我们要看的是追加属性,所以我们去看 appendAttrToArray 函数。

前面两个 if 和我们也没太大关系,我们在最后的 else 加点调试代码看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
protected function appendAttrToArray(array &$item, $key, array|string $name, array $visible, array $hidden): void
{
if (is_array($name)) {
// 批量追加关联对象属性
$relation = $this->getRelationWith($key, $hidden, $visible);
$item[$key] = $relation ? $relation->append($name)->toArray() : [];
} elseif (str_contains($name, '.')) {
// 追加单个关联对象属性
[$key, $attr] = explode('.', $name);
$relation = $this->getRelationWith($key, $hidden, $visible);
$item[$key] = $relation ? $relation->append([$attr])->toArray() : [];
} else {
// 我们新增的调试代码
// var_dump('appendAttrToArray', $key, $name, $visible, $hidden);

$value = $this->getAttr($name);
$item[$name] = $value;

$this->getBindAttrValue($name, $value, $item);
}
}
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
string(17) "appendAttrToArray"
int(0)
string(7) "details"
array(0) {
}
array(2) {
["details"]=>
array(1) {
[0]=>
string(11) "orderNumber"
}
["hongfs"]=>
bool(true)
}

string(17) "appendAttrToArray"
int(1)
string(6) "hongfs"
array(0) {
}
array(2) {
["details"]=>
array(1) {
[0]=>
string(11) "orderNumber"
}
["hongfs"]=>
bool(true)
}

看最后一段的输出,其中 key=1 name=hongfs visible是空数组,hidden 好像是经过处理过的内容。 尝试下在 else 里面处理我们的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected function appendAttrToArray(array &$item, $key, array|string $name, array $visible, array $hidden): void
{
if (is_array($name)) {
// 批量追加关联对象属性
$relation = $this->getRelationWith($key, $hidden, $visible);
$item[$key] = $relation ? $relation->append($name)->toArray() : [];
} elseif (str_contains($name, '.')) {
// 追加单个关联对象属性
[$key, $attr] = explode('.', $name);
$relation = $this->getRelationWith($key, $hidden, $visible);
$item[$key] = $relation ? $relation->append([$attr])->toArray() : [];
} else {
// 修复代码
if(isset($hidden[$name]) && $hidden[$name] === true) {
return;
}

$value = $this->getAttr($name);
$item[$name] = $value;

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

重新访问现在是可以了。

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
[
{
"details": [
{
"productCode": "S18_1749",
"quantityOrdered": 30,
"priceEach": "136.00",
"orderLineNumber": 3
},
{
"productCode": "S18_2248",
"quantityOrdered": 50,
"priceEach": "55.09",
"orderLineNumber": 2
},
{
"productCode": "S18_4409",
"quantityOrdered": 22,
"priceEach": "75.46",
"orderLineNumber": 4
},
{
"productCode": "S24_1937",
"quantityOrdered": 49,
"priceEach": "35.29",
"orderLineNumber": 1
}
],
"orderNumber": 10100,
"orderDate": "2003-01-06",
"requiredDate": "2003-01-13",
"shippedDate": "2003-01-10",
"status": "Shipped",
"comments": null,
"customerNumber": 363,
"delete_time": 0
}
]

上面的 hidden 变成二维数组,是在 toArray 最开始就进行的处理,让我们后面的修复不需要考虑那么多,点赞。

往上