singleton()必须在register()中注册,boot()中无效;应使用::class而非字符串键名;fpm下请求内复用,swoole下需防跨请求状态污染。

singleton()写在boot()里肯定不生效
这是最常见也最容易被忽略的错误:把 $this->app->singleton() 放在服务提供者的 boot() 方法里。容器在 boot() 阶段已经完成依赖解析,此时再注册单例,绑定根本不会被加载——app()->make() 时只会 fallback 到反射构造,相当于没绑。
必须挪到 register() 方法中,且只能在 AppServiceProvider 或自定义服务提供者里做。Laravel 启动流程决定了:只有 register() 是容器绑定的合法时机。
- 错误写法:
boot()中调用$this->app->singleton(CacheContract::class, RedisCache::class) - 正确位置:
register()方法第一行就注册,越早越好 - 验证方式:在控制器里执行
dd(app(CacheContract::class) === app(CacheContract::class)),应为true
类名传字符串还是::class?必须用::class
传字符串如 'App\Services\PaymentService' 看似省事,但一旦命名空间拼错、自动导入没生效或 IDE 重命名失败,运行时直接报 Target [App\Services\PaymentService] is not instantiable,且错误堆栈不提示是绑定问题。
用 PaymentService::class 能让 PHP 在编译期校验类是否存在,IDE 也能跳转和自动补全,是唯一安全写法。
- 危险:
$this->app->singleton('payment.service', PaymentService::class)—— 字符串键名无校验,且键名和类名不一致易混乱 - 推荐:
$this->app->singleton(PaymentService::class, PaymentService::class),接口绑定同理:$this->app->singleton(PaymentGateway::class, AlipayGateway::class) - 如果类有构造依赖(比如需要
HttpClient),确保该依赖也已正确绑定,否则单例创建时会抛出解析异常
FPM 和 Swoole 下单例行为完全不同
在传统 FPM 模式下,每次请求都是全新进程,singleton() 实例只在当前请求内复用,天然隔离;但在 Swoole 常驻内存模式下,单例实例跨请求存活,若内部保存了用户 ID、请求上下文等状态,下一个请求进来就会读到上一个用户的脏数据。
这不是 Laravel 的 bug,而是设计使然。你得自己负责清理或规避。
- 别在单例里存请求级状态(如
$this->userId、$this->requestId) - 若必须带上下文,改用
bind()+ 构造参数注入,或把上下文作为方法参数传入 - Swoole 场景下,可在
handle()开始前手动重置单例状态,或用app()->forgetInstance(PaymentService::class)强制重建(慎用,影响性能)
依赖注入拿不到单例?检查是不是手动 new 了
控制器里写 new PaymentService() 或事件监听器里 new SomeService(),完全绕过了服务容器,哪怕你注册了 singleton() 也没用——类型提示失效、依赖无法注入、单例缓存形同虚设。
真正生效的前提,是整个对象链都由容器创建。
- 控制器必须通过路由访问,或用
app(MyController::class)获取,不能new MyController() - 队列任务、命令类、事件监听器,都要声明为容器可解析的类(即构造函数参数全可被容器 resolve)
- 临时取服务,优先用构造函数注入;非必要不使用
app(PaymentService::class),它每次调用都新建(除非你已设为 singleton)
make() 的硬门槛。











