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

8.6 KiB
Raw Permalink Blame History

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 行)

// 第 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

// 第 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);

其他位置

// 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 行:

'prefix' => 'vrt_',

BaseService.php 第 17 行:

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 时的行为

// 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'

防御建议:

$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, ...}] — 数组。

// 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"]

// 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正确示范

$roomId = !empty($room['id']) ? $room['id'] : ('room_' . $rIdx);
// ✅ 先检查存在性,不存在则生成默认值

AdminGoodsSaveHandle.php:77 缺少空安全:

// 有 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+ 警告

// 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_WARNINGThe '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

// 修复前
$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

// 修复前
$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

// 如果 selectedRoomIds 需要房间对象而不是 ID 列表,修改过滤逻辑
// 当前 array_column(..., null) 已被 array_filter 替代,不需要 array_column
// rooms 数据直接保留(不再是 ID 列表,而是完整房间对象)

9. 关键差异BatchGenerate vs AdminGoodsSaveHandle

SeatSkuService::BatchGenerate 已正确处理空安全(第 100 行):

$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但不会报错

修复三行代码即可解决问题。