bind每次make新建实例,singleton首次创建后永久复用同一实例;前者适用于无状态轻量类如dto、策略类,后者适用于有状态或高开销服务如数据库连接、redis客户端。

关键看每次解析时是否新建实例:bind 每次 app()->make() 都生成新对象,singleton 首次创建后永久复用同一实例。这不是写法差别,而是直接影响状态共享、资源消耗和请求行为的核心机制。
实例生命周期差异一目了然
用一个简单类验证:
bind 绑定:两次 make 得到两个独立对象,修改各自属性互不影响。
app()->bind('example', Example::class);$a = app()->make('example'); $a->id = 1;$b = app()->make('example'); $b->id = 2;- 结果:
$a->id === 1,$b->id === 2,且$a !== $b
singleton 绑定:两次 make 返回同一个对象引用,属性修改会互相可见。
app()->singleton('example', Example::class);$a = app()->make('example'); $a->id = 1;$b = app()->make('example'); $b->id = 2;- 结果:
$a->id === 2,$b->id === 2,且$a === $b(同一内存地址)
该用哪个?看服务有没有“状态”和“开销”
选错会导致连接池耗尽、日志上下文混乱、缓存失效等线上问题。
- 用 singleton:数据库连接、Redis 客户端、日志处理器、HTTP 客户端——它们持有连接、缓冲区或全局配置,必须唯一且复用
- 用 bind:DTO、表单请求类、策略类、格式化器——无内部状态,每次新建更安全,避免属性意外污染
- 不确定时先 bind,观察是否出现状态错乱(如用户 A 的数据出现在用户 B 请求中)或性能瓶颈(如连接数飙升),再切 singleton
绑定位置和写法必须规范
不放在正确位置或写法错误,会让绑定失效或依赖无法注入。
- 必须在服务提供者的
register()方法中绑定,不能在boot()—— 否则部分依赖可能已开始解析,绑定被跳过 - 闭包绑定时,要通过
$app->make()解析依赖,不能直接new:
✅ 正确:$this->app->singleton(Logger::class, function ($app) { return new Logger($app->make('monolog.factory')); });
❌ 错误:$this->app->bind('logger', function () { return new Logger(); });(丢失依赖注入) - 接口绑定同样适用:
$this->app->singleton(CacheContract::class, RedisCache::class);
测试与调试要注意实例残留
singleton 实例在测试中会跨用例存活,引发干扰。
- 单元测试里若修改了 singleton 对象的状态,tearDown 中需手动清理:
app()->forgetInstance(YourService::class); - 验证是否生效,直接比较两次 make 结果:
app('cache') === app('cache')必须为true - 不要用
instance()替代singleton()—— 前者传入的是已构造对象,无法享受容器的依赖自动解析能力











