单例模式在PHP中非必需,仅适用于天然全局唯一、状态需跨请求保持且不可替代的组件;PHP-FPM下为每进程单例,需禁用__clone/__wakeup/__sleep防止绕过构造逻辑,推荐依赖注入容器替代。
单例模式在 PHP 架构里不是“必须用”的设计,而是特定场景下控制资源唯一性的手段;滥用它会直接导致测试困难、隐藏依赖、并发问题和内存泄漏。
Singleton?——看是否真需要全局唯一实例单例只适用于那些「天然全局唯一、状态需跨请求/调用保持、且不可替代」的组件。PHP-FPM 模式下要注意:每个 worker 进程内是独立的单例,不是整个应用全局唯一。
Logger 实例(如写入同一文件,需避免多进程同时 fopen)Redis 或 Memcached 实例,复用连接减少开销)__clone、__wakeup、__sleep 都得禁掉?PHP 的序列化/反序列化和克隆机制会绕过构造逻辑,让单例失效。比如 unserialize() 一个对象可能生成新实例,clone $instance 会复制出第二个对象。
class Config
{
private static ?self $instance = null;
private function __construct() {}
private function __clone() {}
private function __wakeup() {}
private function __sleep() { throw new \Exception('Cannot serialize singleton'); }
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
FPM 是多进程模型,每个请求由独立进程处理,static 变量只在当前进程内有效。你以为的“全局单例”,其实是“每进程一个单例”。
static 存数组?重启 worker 后就丢,且不同进程间不共享 → 改用 Redis 或 APCu(注意 APCu 在 FPM 下默认进程隔离)PDO::ATTR_PERSISTENT)$currentUser)?多个请求混在一起时数据错乱 → 绝对禁止真正需要解耦和可控生命周期时,优先考虑依赖注入容器(如 PHP-DI、Symfony DI),它能明确声明“这个服务是单例作用域”,还能自动处理构造依赖、延迟初始化、循环引用等。
shared: true 等价于单例行为,但可被测试替换、支持 AOP、不污染类内部逻辑static 实现的单例无法被 Mock,导致单元测试必须走真实 DB/Redis;而容器注入的对象可轻松 stubDateTimeZone 实例,直接函数内 new 更轻量,没必要上升到单例单例真正的复杂点不在写法,而在厘清「这个对象的状态是否真的该跨调用存在」「它的生命周期是否和 worker 进程一致」
「下游是否依赖它的内部状态」——这些没想清楚,代码越“规范”越危险。