Laravel/ThinkPHP容器中bind每次make新建实例,singleton首次创建后永久复用;选错会导致连接耗尽、状态污染或内存泄漏:bind适合无状态轻量类,singleton必须用于有状态资源。

bind 每次调用 make() 都新建实例,singleton 第一次创建后永久复用同一个对象——选错会导致内存泄漏、状态污染或连接耗尽。
bind 是“每次发一张新名片”
它只登记抽象名到具体类/闭包的映射,不缓存实例。每次 Container::getInstance()->make('xxx') 都会触发完整构造流程:反射解析依赖 → 递归 make() 依赖项 → 调用构造函数。
- 适合无状态、轻量、需隔离的类,比如
FormRequest、DTO、临时策略对象 - 闭包绑定时,
$app参数可用,但别在里头做耗时操作(如读文件、查 DB),否则每次make()都拖慢响应 - 重复调用
bind('cache', RedisCache::class)会覆盖前一次绑定,但不会影响已存在的实例 - 错误示例:
Container::getInstance()->bind('db', function () { return new PDO(...); });→ 每次请求新建连接,很快打爆 MySQL 连接池
singleton 是“发完名片就锁死这张”
底层调用的是 bind($abstract, $concrete, true),第三个参数 true 表示启用单例标记。首次 make() 后,实例被存进容器的 $instances 数组;后续所有调用直接返回该引用,跳过构造逻辑。
- 必须用于有状态、高开销、需全局一致的服务,比如数据库连接、Redis 客户端、日志器
- 验证是否生效:两次
make()返回的对象===必须为true - 接口绑定也支持:
Container::getInstance()->singleton(CacheInterface::class, RedisCache::class) - 常见坑:在
common.php或非生命周期钩子里调用singleton()—— 上下文未初始化,$instances可能为空或被覆盖,导致单例失效
什么时候不能混用?
ThinkPHP 8 的容器不支持运行时切换绑定类型。一旦对同一抽象名先后调用 bind() 和 singleton(),后者会覆盖绑定定义,但**不会清理已通过 bind() 创建并缓存在 $instances 中的旧实例**——这会造成内存残留和行为不一致。
立即学习“PHP免费学习笔记(深入)”;
- 例如先
bind('sms', AliyunSms::class),再singleton('sms', TencentSms::class),之前通过make()得到的AliyunSms实例仍驻留内存,且无法被自动回收 - 单元测试中若修改了 singleton 实例的状态(如 mock 了某个方法返回值),必须手动调用
Container::getInstance()->forgetInstance('sms'),否则污染后续测试 - 绝对不要在
common.php中调用任何绑定方法——该文件加载时机早于应用上下文建立,绑定可能被后续请求覆盖
真正容易被忽略的是:单例不是“全局变量”,而是容器生命周期内的唯一实例;如果服务本身持有外部资源(如 socket、file handle),必须确保其析构逻辑被正确触发,否则在 CLI 命令或长生命周期任务中会累积泄漏。



















