bind()每次新建实例,适合无状态轻量类;singleton()首次创建后复用,适用于有状态资源;instance()直接注入对象,跳过构造逻辑——三者对应不同生命周期,不可混用。

用错 bind()、singleton() 或 instance(),不是“功能没生效”,而是连接泄漏、状态污染、测试失败——这三种方法根本不是“换着用”的选项,而是对应三类完全不同的对象生命周期和依赖关系。
bind() 每次都 new,适合无状态轻量类
它注册的是“怎么造”,不是“造好的东西”。每次 app()->make() 都执行闭包或反射构造,不缓存结果。典型适用:DTO、策略类、表单验证器、临时转换器。
- 误用后果:把
DatabaseConnection或RedisClient用bind()→ 每次请求新建连接,连接池几秒打满,报PDOException: Too many connections - 正确写法:
$this->app->bind(FormatterInterface::class, JsonFormatter::class);闭包里必须显式return实例,比如function ($app) { return new Logger('api'); } - 注意:闭包内调用
$app->make()安全,但别放耗时逻辑(如读磁盘配置),否则每次解析都拖慢响应
singleton() 首次构造后复用,有状态资源唯一正解
它确保整个请求生命周期(或 CLI 进程)只创建一次实例,并缓存返回。这是数据库连接、缓存仓库、HTTP 客户端等资源型服务的强制要求。
- 常见错误:在
boot()方法里写singleton()→ 容器已开始解析依赖,绑定被跳过,尤其在队列任务或 Artisan 命令中必现 - 验证是否生效:
app()->make('cache') === app()->make('cache')必须为true - 接口也能单例绑定:
$this->app->singleton(CacheContract::class, RedisCache::class) - 单元测试中若修改了 singleton 实例的状态,必须手动
app()->forgetInstance(CacheContract::class),否则污染后续测试
instance() 不走构造逻辑,只硬塞你给的对象
它跳过所有反射、依赖解析、生命周期管理,直接把传入的对象存进容器。适用于 Mock 注入、外部 SDK 初始化完成的客户端、或需要精确控制销毁时机的场景。
- 致命风险:若该实例依赖未初始化的组件(如未配置 DB 的 Eloquent Model),后续调用直接抛
RuntimeException - 典型用法:
$mock = Mockery::mock(SmsService::class); $this->app->instance(SmsService::class, $mock); - 不能混用:一旦用了
instance(),就等于放弃容器对这个抽象的构造控制权,后续任何bind()或singleton()对它都无效
接口绑定不是语法糖,是契约替换的基础设施
绑定接口到实现(如 $this->app->bind(EventPusher::class, RedisEventPusher::class))不是为了“看起来高级”,而是让业务代码只依赖契约,不耦合具体实现。
- 切换成本归零:换消息队列从 Redis 改成 Kafka,只需改一行绑定,不用动任何控制器或服务类
- 必须配合类型提示使用:构造函数参数写
EventPusher $pusher,容器才能识别并注入 - 别名绑定要小心:
$this->app->bind('event.pusher', RedisEventPusher::class)后,app('event.pusher')可用,但类型提示失效,IDE 无法跳转,也不支持自动注入
真正难的不是记住五种方式,而是判断一个类到底有没有状态、依赖是否就绪、是否需要跨请求复用——这些决定了你该选哪条路,而不是哪个方法更“高级”。


















