工具类单例须用static关键字因静态方法无法访问$this,需用self::或static::操作静态成员;是否用单例取决于是否需共享状态:纯函数操作用静态方法,资源持有(如DB连接)必须单例对象。

PHP 静态方法不能直接访问 $this,所以在工具类单例中必须用 self:: 或 static:: 调用静态成员,否则会报 Fatal error: Using $this when not in object context。
为什么工具类单例里总要用 static 关键字?
单例的核心是全局唯一实例,而工具类通常不依赖状态,所以倾向用静态方法封装功能(比如 StringUtils::trimEmpty())。但若单例本身需要延迟初始化、或内部要复用实例状态(如数据库连接、缓存句柄),就不能全写成静态方法——此时得把构造器设为 private,用 private static $instance 存实例,再提供 public static getInstance() 来获取。
常见错误:在 getInstance() 里写 $this->init(); —— 这里 $this 尚未存在,PHP 会直接报错。
- 正确写法是
self::$instance = new static();+self::$instance->init(); - 如果子类继承该单例基类,用
static::更安全(支持后期静态绑定) - 别在
__construct()里调用$this->xxx()做重操作,容易导致 newInstance 失败且无提示
静态方法 vs 单例对象方法:什么时候该用哪个?
判断依据不是“看起来方便”,而是“是否需要共享状态”。比如日志工具类:Logger::info() 可以纯静态,因为每次只是发一条消息;但如果你的 HttpClient 工具类要复用 cURL 句柄、保持 cookie、设置默认 header,那就必须走单例对象方式,否则每次调用都重建连接,性能崩塌。
立即学习“PHP免费学习笔记(深入)”;
- 纯函数式操作(字符串处理、数组转换、时间格式化)→ 用静态方法,简单高效
- 带资源持有(PDO 实例、Redis 连接、配置上下文)→ 必须单例对象,静态方法无法管理生命周期
- 混合场景(如配置中心:读取是静态,但监听变更需长连接)→ 单例对象更可控,静态方法只作快捷入口(
Config::get()内部仍委托给self::getInstance()->get())
PHP 8.1+ 属性提升让单例写法更干净,但要注意 readonly 的陷阱
以前单例常把 $instance 声明为 private static,PHP 8.1 后可以结合 readonly 和构造器属性提升,但别误用:readonly 是针对实例属性的,static 属性不能加 readonly。有人试过 private static readonly self $instance; → 直接语法错误。
- 正确的简化写法仍是:
private static ?self $instance = null; - 如果想防外部修改实例,靠的是
private+getInstance()返回引用,而不是 readonly - 构造器里别对
static属性做复杂赋值(比如从文件加载大配置),否则首次调用getInstance()会卡住整个请求
真正难处理的从来不是怎么写单例,而是当多个单例互相依赖(A 依赖 B,B 又在构造时依赖 A)时,静态初始化顺序会悄无声息地导致 null 引用或循环调用——这种问题不会报错,只会返回空结果,调试起来特别费时间。











