本文目录导读:

在 PHP 开发中,“决策记录”通常指架构决策记录(ADR,Architecture Decision Record),或者是业务逻辑中的决策日志,我会从这两个维度分别给出落地方案。
架构决策记录(团队协作 / 文档)
这是给开发者看的,记录“为什么”要这么写代码,推荐使用轻量级的 Markdown 文件,放进代码仓库。
标准目录结构(ADR 模式)
在项目根目录创建 docs/adr/ 文件夹,每个决策一个文件,用递增数字命名:
docs/adr/
├── 0001-use-vite-as-build-tool.md
├── 0002-adopt-repository-pattern.md
└── 0003-use-redis-for-session-storage.md
标准模板(避免写废话)
# 3. 使用 Redis 替代文件存储 Session - 日期: 2025-05-20 - 状态: 已接受 (Accepted) // 可选:提议/已接受/已废弃 - 决策者: 张三、李四 ## 背景 当前使用 PHP 原生 `$_SESSION`,在负载均衡多机部署时,会话数据不同步,导致用户频繁掉线。 ## 决策 采用 Redis 作为会话存储驱动(使用 `phpredis` 扩展)。 ## 后果 - 正面: 支持横向扩展,会话读取速度快。 - 负面: 运维需额外维护 Redis 高可用;若 Redis 宕机,所有用户会话失效。 ## 替代方案 - 使用数据库存储(太慢)。 - 使用 JWT 无状态 Token(改动前端较大,暂缓)。
实施要点
- 写“为什么”:记录当时考量的技术约束和业务场景,别记“用了什么函数”。
- 状态流转:如果方案被推翻,不要删除文件,将原文件状态改为“已废弃”,并新建 0004 文件说明新方案,保留历史脉络。
业务决策日志(运行时追踪)
这是给业务方或排查问题时看的,记录用户或系统在运行时的关键操作选择。
建议不要直接 error_log() 乱写,应该结构化地记录到日志表或文件。
核心类封装(决策记录器) 使用单例模式注册到容器,方便在任何地方调用。
<?php
namespace App\Services;
use Psr\Log\LoggerInterface;
class DecisionLogger
{
private array $context = [];
public function __construct(private LoggerInterface $logger) {}
/**
* 开始记录一次业务决策过程
*/
public function start(string $processName, string $userId, array $initData = []): void
{
$this->context = [
'process' => $processName,
'user_id' => $userId,
'start_time' => microtime(true),
'steps' => [],
];
$this->log('决策开始', $initData);
}
/**
* 记录每一步判断
*/
public function step(string $decisionPoint, string $result, array $reason = []): void
{
$this->context['steps'][] = [
'point' => $decisionPoint,
'result' => $result,
'reason' => $reason,
];
// 写入系统日志或异步队列
$this->logger->info("决策节点: {$decisionPoint} -> {$result}", $reason);
}
/**
* 结束记录并归档
*/
public function finish(string $finalResult): void
{
$elapsed = microtime(true) - $this->context['start_time'];
// 这里可以推送到 ElasticSearch 或存入数据库表
$this->logger->notice("决策完成: {$finalResult}", [
'process' => $this->context['process'],
'steps' => $this->context['steps'],
'duration_ms' => round($elapsed * 1000, 2),
]);
}
}
在业务逻辑中的使用(优酷案例:风控/推荐/优惠券)
假设你在做订单折扣计算:
public function calculateDiscount(Order $order, DecisionLogger $logger): float
{
$logger->start('order_discount', $order->user_id, ['order' => $order->id]);
// 如过用户是 VIP
if ($order->user->isVip()) {
$logger->step('vip_check', '通过', ['vip_level' => $order->user->vip_level]);
$rate = 0.9;
} else {
$logger->step('vip_check', '未通过', ['err' => '用户非VIP']);
$rate = 1.0;
}
// 如果金额大于 1000 使用大额券
if ($order->total > 1000) {
$logger->step('threshold_check', '命中大额券', ['total' => $order->total]);
$rate -= 0.05;
} else {
$logger->step('threshold_check', '未达门槛');
}
$final = $order->total * max(0.1, $rate);
$logger->finish("最终折扣: {$final}");
return $final;
}
存储方案选择(重要)
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 低并发、内部系统 | 写入 MySQL 日志表 | 好查好看,配合后台管理界面过滤用户ID |
| 高并发、核心链路 | 写入 InfluxDB/TDengine 时序数据库或 Redis Stream | 异步批量落盘,不阻塞主流程 |
| 需要全文检索 | 直接输出 JSON 到 ELK (ElasticSearch) | 后续可按关键词搜“为什么没送券” |
技术选型决策(代码注释里的决策)
在代码里遇到关键的 IF/ELSE 或设计模式选择时,除了魔法数字,可以加一个标准化的注释块:
/**
* 决策:为什么不直接用 `==` 判断?
* 原因:`0 == "foo"` 在 PHP 8.0 之前的版本会返回 true,这里必须严格比较类型。
* 成本:牺牲少量代码简洁性,换取类型安全。
*/
if ($inputValue === 0) { // 严格的 Int 比较
// ...
}
总结给 PHP 开发者的建议
- 团队决策:用 ADR 放进
docs/adr/,GIT 提交时一起走 PR 审核,记录演进史。 - 运行决策:封装一个
DecisionLogger类,关键节点埋点,日志格式统一为point -> result,方便排查“为什么走到了这个分支”。 - 核心原则:决策记录是给未来的自己看的,犯过的错不重复,想过的理由不重想。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播