三部曲完结篇,讲区分老手和新手的高级模式:长期进程的内存管理、现代并发原语、生产环境的运维改进、小而关键的 API 改进。
问题:普通数组给对象附加元数据会阻止垃圾回收,长期进程(worker、ReactPHP 服务器)慢慢泄漏内存。
什么时候用:缓存、每对象元数据、监听器注册表,元数据不该让对象一直活着。
$cache = new WeakMap();
$user = new User(id: 123);
$cache[$user] = expensiveOperation($user);
// 稍后...
unset($user);
// $cache 条目自动移除,GC 能释放内存效果:临时元数据在主对象回收时消失——长期进程内存稳定。
建议:WeakMap 用对象身份(不是 ID)做键的临时缓存。
问题:轻量级并发时的回调地狱或复杂 promise 链。
什么时候用:自定义异步层、数千并发 I/O 的 CLI 工具、事件循环(Amp、ReactPHP)集成。
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('paused');
echo "Resumed with: $value\n";
});
$val = $fiber->start(); // 启动,返回 'paused'
$fiber->resume('hello'); // 恢复,打印 "Resumed with: hello"效果:写非阻塞、线性的代码流,干净地 yield 和 resume。
建议:优先用成熟的异步库(Amp、ReactPHP),它们基于 Fibers——别自己重新实现多路复用器。
问题:文件 session 本地行,多节点和扩展时就崩了。
什么时候用:水平扩展应用,必须共享 session 状态。
class RedisSessionHandler implements SessionHandlerInterface {
public function __construct(private Redis $redis) {}
public function read(string $id): string {
return (string) $this->redis->get("sessions:$id");
}
public function write(string $id, string $data): bool {
return $this->redis->setex("sessions:$id", 3600, $data);
}
// 实现 open, close, destroy, gc...
}
session_set_save_handler(new RedisSessionHandler($redis));效果:快速集中的 session(Redis、memcached、数据库),实例重启后还在,配合负载均衡器。
建议:用固定键前缀(sessions:)和专用 Redis DB 存 session。
问题:高吞吐应用重复编译 opcode 有开销。
什么时候用:稳定的生产代码,知道哪些类是热点。
// config/preload.php
opcache_compile_file('/app/vendor/autoload.php');
opcache_compile_file('/app/src/Service/Foo.php');
// php.ini: opcache.preload=/path/to/config/preload.php效果:热类预编译到共享内存,请求延迟更低、启动成本更小。
建议:配合 composer install --classmap-authoritative 保持 preload 列表紧凑。
问题:父类或接口方法名/签名改了,出现静默 bug。
什么时候用:子类或实现类覆盖父方法时。
interface LoggerInterface {
public function log(string $message): void;
}
class FileLogger implements LoggerInterface {
#[\Override]
public function log(string $message): void {
// ...
}
}效果:PHP 8.3 在 #[Override] 没实际覆盖父类/接口方法时报编译错误——重构的免费护栏。
建议:广泛用 #[Override];PHP ≥ 8.3 的静态分析和 CI 能早点抓回归。
(注意:#[Override] 从 PHP 8.3 开始引擎强制;旧版本上是空的。)
问题:接受字符串或带 __toString() 对象的 API 要笨拙检查。
什么时候用:日志、模板、接受字符串或能转字符串的领域对象的 API。
function writeToLog(Stringable|string $message): void {
file_put_contents('/tmp/log', (string) $message . PHP_EOL, FILE_APPEND);
}
writeToLog("plain text");
writeToLog(new class { public function __toString(){ return "object text"; }});效果:函数签名更清楚,编译时就知道允许什么输入。
建议:Stringable 配 union types 让 API 灵活又有类型。
假设高吞吐订单处理器用了三篇的模式。readonly 命令处理器(第 1 篇)用 WeakMap 做每请求缓存,match 做状态映射(第 2 篇),Redis 做快速查找(第 3 篇):
#[AsMessageHandler]
readonly class ProcessOrderHandler {
public function __construct(
private OrderRepository $repo,
private Redis $redis,
private LoggerInterface $logger
) {}
public function __invoke(ProcessOrder $cmd): void {
static $ctx = new WeakMap();
$priority = match ($cmd->type) {
'express' => 'high',
default => 'normal',
};
$this->logger->info('order.processing', [
'id' => (string) $cmd->orderId,
'priority' => $priority,
]);
$this->redis->setex("order:summary:{$cmd->orderId}", 3600, serialize($this->repo->summary($cmd->orderId)));
}
}这就是语言特性配运维模式写出的紧凑、健壮、生产级代码。
| 特性 | 使用场景 | PHP 版本 |
|---|---|---|
| 弱映射 | 避免泄漏 | 8.0 |
| 纤程 | 异步 IO | 8.1 |
| 会话处理器 | 扩展 session | legacy |
| 预加载 | 加速 OPcache | 7.4 |
| Override | 安全重构 | 8.3 |
| 可字符串化接口 | 字符串 API | 8.0 |
三篇的特性不是语法糖——是架构工具。
第一篇:声明意图(类型、attributes、enums)
第二篇:表达逻辑(match、生成器、null-safe)
第三篇:构建弹性系统(弱映射、纤程、预加载)
选一个能解决代码库痛点的模式,迁移一个模块试试。ROI 立竿见影——后面的路就清楚了。
三部曲完结。去做点厉害的东西吧。想好先用哪个特性了吗?告诉我你的痛点,我帮你找个小迁移方案。