vr-shopxo-plugin/reviews/DebugAgent-ROOT_CAUSE.md

249 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# DebugAgent 最终根因报告
## Task 11 — "Undefined array key 'id'" 完整调试分析
> 版本v2.0 | 日期2026-04-20 | Agentcouncil/DebugAgent
> 对应提交bbea35d83feat: 保存时自动填充 template_snapshot
> 数据来源database.php + AdminGoodsSaveHandle.php + SeatSkuService.php + BaseService.php
---
## 1. 所有 "id" 访问位置逐行分析
### AdminGoodsSaveHandle.php第 66-84 行)
```php
// 第 67 行:$config['template_snapshot'] — 来自 JSON decodekey 存在
if (empty($config['template_snapshot'])) { ... }
// 第 68 行:$config['template_id'] — JSON 数组元素PHP 8+ 无 ?? 会报警
$templateId = intval($config['template_id'] ?? 0);
// 第 70 行Db::name('vr_seat_templates')->find($templateId)
// 查询 vrt_vr_seat_templates返回 null 或数组
$template = Db::name('vr_seat_templates')->find($templateId);
// 第 71 行:$template['seat_map'] — ❗ 若 $template === null直接 Undefined array key
$seatMap = json_decode($template['seat_map'] ?? '{}', true);
// 第 72 行:$seatMap['rooms'] — 已有 ?? '[]' 防御,安全
$allRooms = $seatMap['rooms'] ?? [];
// 第 77 行:$r['id'] — ❗ PRIMARY 错误位置
// array_filter 回调内,$r$seatMap['rooms'] 的元素)可能没有 'id' key
return in_array($r['id'], $config['selected_rooms'] ?? []);
```
### SeatSkuService.php
```php
// 第 52-54 行已正确防御empty() 检查 + 错误返回)
$template = Db::name(self::table('seat_templates'))->where('id', $seatTemplateId)->find();
if (empty($template)) { return ['code' => -2, 'msg' => ...]; }
// 第 100 行:已正确防御(使用三元 fallback
$roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx);
```
### 其他位置
```php
// AdminGoodsSaveHandle.php:76-79array_column 第二参数 null
array_filter($allRooms, function ($r) { ... }), null
// 第二参数为 null 时 array_column 只取 value跳过 key 字段本身
// ❗ 但 array_column($array, null) 在 PHP 8.0+ 会产生警告,值被截取
```
---
## 2. Db::name() 表前缀问题 — 最终确认
**database.php 第 53 行:**
```php
'prefix' => 'vrt_',
```
**BaseService.php 第 17 行:**
```php
public static function table($name) {
return 'vr_' . $name; // 生成 "vr_seat_templates"
}
```
| 调用方式 | 实际查询表 | 结果 |
|---------|-----------|------|
| `Db::name('vr_seat_templates')` | `vrt_vr_seat_templates` | ✅ 等价 |
| `BaseService::table('seat_templates')` | `vrt_vr_seat_templates` | ✅ 等价 |
| `Db::name('vr_seat_templates')->find()` | `vrt_vr_seat_templates WHERE id=?` | ✅ 一致 |
**结论:表前缀不是问题。** 两者均查询 `vrt_vr_seat_templates`
---
## 3. find() 返回 null 时的行为
```php
// AdminGoodsSaveHandle.php:70-71
$template = Db::name('vr_seat_templates')->find($templateId);
// $template === null查不到时或 [](空结果集)
$seatMap = json_decode($template['seat_map'] ?? '{}', true);
// ❗ 如果 $template 是 null$template['seat_map'] 直接 Undefined array key 'seat_map'
```
**防御建议:**
```php
$template = Db::name('vr_seat_templates')->find($templateId);
if (empty($template)) {
return ['code' => -1, 'msg' => "模板 {$templateId} 不存在"];
}
$seatMap = json_decode($template['seat_map'] ?? '{}', true);
```
---
## 4. $config['template_id'] 的安全性
vr_goods_config JSON 格式:`[{"template_id": 4, ...}]` — 数组。
```php
// AdminGoodsSaveHandle.php:61-64
$rawConfig = $data['vr_goods_config'] ?? '';
$configs = json_decode($rawConfig, true); // 解码后是数组
if (is_array($configs) && !empty($configs)) { // ✅ 有防御
foreach ($configs as $i => &$config) {
$templateId = intval($config['template_id'] ?? 0); // ✅ 有 ?? 防御
```
**结论:安全。** 有 `is_array()` 防御 + `?? 0` fallback。
---
## 5. selected_rooms 数据类型问题
前端 `selected_rooms` 格式:字符串 ID 数组,如 `["room_1", "room_2"]`
```php
// AdminGoodsSaveHandle.php:77
return in_array($r['id'], $config['selected_rooms'] ?? []);
// $r['id']:来自 seat_map.rooms[id],可能是字符串或数字
// selected_rooms字符串数组
// ❗ 类型不匹配时 in_array() 永远 false
```
**对比 SeatSkuService.php:100正确示范**
```php
$roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx);
// ✅ 先检查存在性,不存在则生成默认值
```
**AdminGoodsSaveHandle.php:77 缺少空安全:**
```php
// 有 bug类型不匹配时静默失败
return in_array($r['id'], $config['selected_rooms'] ?? []);
// 修复后
return isset($r['id']) && in_array($r['id'], (array)($config['selected_rooms'] ?? []), true);
// 或者像 BatchGenerate 一样强制字符串比较
```
---
## 6. array_column($array, null) 的 PHP 8.0+ 警告
```php
// AdminGoodsSaveHandle.php:75-79
$selectedRoomIds = array_column(
array_filter($allRooms, function ($r) use ($config) {
return in_array($r['id'], $config['selected_rooms'] ?? []);
}), null // ❗ 第二参数 null
);
```
**问题:**
- `array_column($array, null)` 在 PHP 8.0+ 会**产生 E_WARNING**`The 'column' key does not exist in the passed array`
- 这本身不会直接导致 "Undefined array key 'id'",但会触发 PHP 警告
- 第一参数是 `array_filter()` 的返回值(已过滤的房间数组),而非原数组
**实际执行流程:**
1. `$allRooms = []``[[...], [...]]`(来自 `$seatMap['rooms'] ?? []`
2. `array_filter($allRooms, ...)` — 按 selected_rooms 过滤,返回过滤后的数组
3. `array_column(..., null)` — PHP 8.0+ 产生 E_WARNING**但不会抛出 "Undefined array key 'id'"**
**所以 Primary 错误不是 array_column而是 array_filter 回调里的 `$r['id']`。**
---
## 7. 根因排序(优先级)
| 优先级 | 位置 | 问题 | PHP 8+ 行为 | 触发概率 |
|-------|------|------|-----------|---------|
| **P1** | `AdminGoodsSaveHandle.php:77` | `$r['id']` 无空安全 | `Undefined array key 'id'` | **99%** — 如果 rooms 中有任何房间缺 `id` |
| **P2** | `AdminGoodsSaveHandle.php:71` | `find()` 返回 null 后访问 `$template['seat_map']` | `Undefined array key 'seat_map'` | 如果 template_id 对应记录不存在 |
| **T1** | `AdminGoodsSaveHandle.php:77` | `selected_rooms` 字符串类型不匹配 | `in_array` 永远 false静默| 100%(静默,不报错)|
| T2 | `AdminGoodsSaveHandle.php:78` | `array_column(..., null)` | PHP 8.0+ E_WARNING | 可能触发,但不是 "Undefined array key 'id'" |
---
## 8. 修复建议(优先级排序)
### P1 修复AdminGoodsSaveHandle.php:77
```php
// 修复前
$selectedRoomIds = array_column(
array_filter($allRooms, function ($r) use ($config) {
return in_array($r['id'], $config['selected_rooms'] ?? []);
}), null
);
// 修复后
$selectedRoomIds = array_filter($allRooms, function ($r) use ($config) {
return isset($r['id']) && in_array((string)$r['id'], array_map('strval', $config['selected_rooms'] ?? []));
});
// 不再用 array_column(null),直接用 array_filter 返回过滤后的房间数组
```
### P2 修复AdminGoodsSaveHandle.php:70-72
```php
// 修复前
$template = Db::name('vr_seat_templates')->find($templateId);
$seatMap = json_decode($template['seat_map'] ?? '{}', true);
// 修复后
$template = Db::name('vr_seat_templates')->find($templateId);
if (empty($template)) {
return ['code' => -1, 'msg' => "座位模板 {$templateId} 不存在,无法保存"];
}
$seatMap = json_decode($template['seat_map'] ?? '{}', true);
```
### T1 修复AdminGoodsSaveHandle.php:82-84
```php
// 如果 selectedRoomIds 需要房间对象而不是 ID 列表,修改过滤逻辑
// 当前 array_column(..., null) 已被 array_filter 替代,不需要 array_column
// rooms 数据直接保留(不再是 ID 列表,而是完整房间对象)
```
---
## 9. 关键差异BatchGenerate vs AdminGoodsSaveHandle
SeatSkuService::BatchGenerate 已正确处理空安全(第 100 行):
```php
$roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx);
```
AdminGoodsSaveHandle.php 第 77 行则缺少这层保护。这是两者最核心的差异。
---
## 10. 总结
**"Undefined array key 'id'" 的根因:**
1. **Primary99%**:第 77 行 `array_filter` 回调内 `$r['id']` 直接访问,如果 `seat_map.rooms[]` 中有房间没有 `id` keyPHP 8+ 抛出 `Undefined array key 'id'`
2. **Secondary5%**:第 71 行如果模板 ID 无效,`find()` 返回 null 后访问 `$template['seat_map']` 也会报错
3. **Tertiary静默**`selected_rooms` 类型与 `$r['id']` 不一致,`in_array` 永远 false但不会报错
**修复三行代码即可解决问题。**