单例模式必须私有化构造、克隆和反序列化方法,并通过懒加载的getinstance()控制唯一实例;全局变量不可控、无初始化保障、无法延迟加载且难以测试。

getInstance() 是替代全局变量最直接、最可控的方式,但不是“开箱即用”的魔法——它需要你主动封装类、控制访问路径,并承担状态共享带来的副作用。
为什么不能直接用 global 变量代替单例?
全局变量(如 $db 或 $config)在 PHP7.1 中仍可声明,但问题不在语法是否允许,而在运行时行为不可控:
– 它可能被任意脚本意外覆盖或 unset
– 无法保证初始化顺序(比如 require 前就访问了未初始化的 $db)
– 没有延迟加载能力,哪怕从不使用也会在包含时执行构造逻辑
– 单元测试中无法 mock 或重置,导致测试间污染
__construct() 必须私有,否则单例失效
这是最容易漏掉的一环。PHP7.1 不强制检查构造函数可见性,但只要留着 public __construct(),外部就能绕过 getInstance() 直接 new:
– new DatabaseConnection() 会成功,且和 getInstance() 返回的不是同一个对象
– 多个连接实例可能同时打开,消耗数据库连接数
– 状态(如缓存、计数器)不同步
必须写成:private function __construct() { ... }
注意:PHP7.1 支持 private 和 protected 构造函数,但 public 就等于放弃控制权。
static $instance 在 PHP7.1 中的初始化陷阱
PHP7.1 允许静态属性初始化为 null,但不能直接赋值复杂表达式(如 new self())。常见错误写法:private static $instance = new self(); // Parse error: syntax error
正确做法是懒加载(Lazy Loading):
– 在 getInstance() 内部判断 self::$instance === null
– 第一次调用才 new,后续直接返回已存实例
– 避免脚本加载阶段就触发耗时操作(如 PDO 连接)
另外,PHP7.1 的静态变量作用域严格绑定类,不会跨继承链自动共享;子类若需独立单例,必须重写 getInstance() 并管理自己的 static $instance。
别忘了 __clone() 和 __wakeup()
PHP7.1 默认允许 clone 和 unserialize,如果不显式禁止:
– clone DatabaseConnection::getInstance() 会生成新对象,破坏唯一性
– unserialize(serialize($obj)) 可能绕过构造函数,创建第二个实例(尤其在 session 或 cache 场景)
必须补全:private function __clone() {}public function __wakeup() { throw new \Exception("Cannot unserialize singleton"); }
注意:__wakeup() 在 PHP7.4+ 才被官方推荐用于单例防护,但在 PHP7.1 中仍有效——只要它存在且抛异常,反序列化就会中断。
getInstance() 被到处调用,你就很难再把它换成依赖注入,也很难在测试中隔离状态。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











