无状态类用 bind(),有状态资源(如数据库连接、缓存客户端)必须用 singleton();bind 每次新建实例,singleton 首次创建后全局复用,错误使用会导致连接耗尽或状态错乱。

bind() 和 singleton() 到底该用哪个?看依赖有没有状态
结论很直接:无状态类用 bind(),有状态资源(比如数据库连接、缓存客户端、HTTP 客户端)必须用 singleton()。用错一个,轻则内存泄漏,重则数据库连接池被打爆。
常见错误现象:DB::connection() 每次调用都新建 PDO 实例 → MySQL 报 Too many connections;Cache::driver('redis') 每次都新建 PhpRedis 实例 → Redis 连接数飙升。
-
bind()注册的是“配方”,每次app()->make()都执行一次构造逻辑,不缓存 -
singleton()首次解析后把实例存进$instances,后续全走缓存,地址相同 - 验证是否生效:执行
app()->make('cache') === app()->make('cache')必须返回true - 接口绑定也支持单例:
$this->app->singleton(CacheContract::class, RedisCache::class)
instance() 不是 shortcut,是硬塞——必须确保对象已 ready
instance() 跳过所有反射和依赖解析,直接把传入对象塞进 $instances。它不是“省事写法”,而是“接管控制权”的操作。
致命风险:传入一个未初始化完成的对象,比如 new TwilioSmsService() 但 config('services.twilio') 还没加载好 → 后续一调用就抛 Undefined index: twilio_sid。
- 必须手动确保依赖就绪:
$this->app->instance(SmsContract::class, new TwilioSmsService(config('services.twilio'))) - 不能混用:
instance()之后再调singleton(),后者不会覆盖前者 - 测试中若修改了
instance()注入的实例状态,需手动app()->forgetInstance()清理,否则污染后续测试
绑定写在 register() 还是 boot()?顺序错了就失效
服务提供者的 register() 是唯一安全的绑定入口。写在 boot() 里,容器可能已经启动依赖解析流程,你的绑定会被跳过——尤其在队列任务、Artisan 命令或中间件中必现。
典型误用场景:在 boot() 里调 $this->app->singleton(...) → 控制器里 app()->make() 返回 null 或旧实现。
- 所有绑定(
bind/singleton/instance)都应放在register()方法内 -
boot()只能用于“使用”服务,比如监听事件、注册路由、配置中间件 - 如果绑定需要其他服务(如 config),闭包里可安全调用
$app->make('config'),但别做耗时操作
自动注入失效?先查类型提示和命名空间
控制器/服务类里写了 public function __construct(SomeService $service) 却报 Target [SomeService] is not instantiable,90% 是路径或命名空间问题,不是容器坏了。
常见错误现象:IDE 自动导入用了 use App\Services\SomeService;,但实际类文件在 app/Services/SomeService.php,而 PSR-4 映射没配对 → 自动加载失败 → 反射拿不到类定义 → 容器无法构造。
- 确认类文件路径与命名空间严格匹配(注意大小写,Linux 下敏感)
- 检查
composer.json的autoload.psr-4是否包含对应映射 - 运行
composer dump-autoload -o强制刷新自动加载 - 接口绑定必须先声明接口,再
bind(Interface::class, Impl::class),不能只 bind 实现类
最常被忽略的点:容器不是“存对象的盒子”,而是“造对象的配方簿”。你改了配方(绑定),但没清掉旧成品($instances),下次 make() 还是返回老实例。调试时别只盯代码,记得 app()->forgetInstance() 或重启 artisan serve。











