Facade默认返回单例对象,即同一请求内多次调用如Db::table()获取的是同一个Connection实例;仅当设置$alwaysNewInstance=true、调用Facade::instance()或容器强制新建时才返回新实例。

Facade 默认返回单例对象,不是每次调用都 new 一个
ThinkPHP 的 Facade 静态调用(如 Db::table()、Cache::get())背后实际调用的是容器中解析出的对象实例。这个实例默认是单例(Singleton),即同一请求生命周期内,多次调用 Db::query() 或 Db::table() 拿到的都是同一个 think\db\Connection 实例。
关键控制点在 think\Facade::$alwaysNewInstance 属性 —— 它默认为 false,所以 createFacade() 内部调用 Container::make() 时走的是单例缓存路径。
- 除非显式设置
protected static $alwaysNewInstance = true;,否则不会每次都新建 - 即使你在
getFacadeClass()返回的是类名(如'app\common\Test'),只要没设$alwaysNewInstance = true,容器仍会复用已创建的实例 - 手动绑定时(
Facade::bind('test', 'app\common\Test')),同样受该开关影响
什么时候会拿到新对象?
只有以下情况才会打破单例行为,得到全新实例:
- 在 Facade 子类中显式声明
protected static $alwaysNewInstance = true; - 调用
Facade::instance()并传入参数(它会绕过单例缓存,强制make($class, $args, true)) - 通过容器直接调用
app('db', [], true)(第三个参数为true表示强制新实例)
注意:Facade::instance() 是公开方法,但日常开发极少需要它;滥用会导致连接复用失效、事务异常或 Redis select 状态错乱等问题。
立即学习“PHP免费学习笔记(深入)”;
学术新闻稿撰写专业工具。包含规范的五段式写作结构、全流程工作步骤、常见问题解决方案和效率提升技巧。**当以下情况时使用此 Skill**:(1) 需要撰写学术机构、科研团队的新闻通稿/宣传稿;(2) 需要将学术论文、科研成果转化为面向公众的科普性新闻稿;(3) 用户提到"学术新闻稿"、"科研宣传稿"、"论文新闻稿...
Db::table() 多次调用为什么能链式操作?
这不是因为每次 table() 都返回新对象,而是因为 think\db\Query 实例本身是无状态的「构建器」,它的方法(where()、order()、select())只修改自身属性,不改变底层连接状态。真正执行 SQL 时才通过 $this->connection(来自单例的 Db 实例)发起请求。
-
Db::table('user')→ 返回新Query对象(无状态、每次新建) -
Db::table('user')->where(...)->select()→ 所有链式调用都在这个新Query上,最终委托给同一个Connection实例执行 - 所以「Facade 返回对象」和「链式构建器对象」是两层:Facade 保证 Connection 单例,Query 负责临时组装
容易踩的坑:Redis select 后状态残留
这是最典型的单例副作用场景。比如你写:
$redis = Cache::store('redis')->getHandler();
$redis->handler->select(2);
Cache::set('key', 'val'); // 这里也落在 DB 2!
问题在于 Cache::store('redis') 返回的仍是同一个驱动实例(单例),而 $redis->handler 是 PhpRedis 客户端,它的 select() 是连接级状态变更。后续所有通过该实例的缓存操作都会延续 DB 2,除非你再 select(0) 切回来。
- 不要假设“我切过一次就一直有效”,每次敏感操作前应显式
select() - 更安全的做法是封装成独立方法,每次获取 handler 后立即
select(n),避免跨业务污染 - 若需并发隔离,考虑用
app('cache.redis', [], true)强制新实例(但注意连接池开销)
单例本身没错,错在忽略了底层资源(如 Redis 连接)的状态可变性 —— 这是 Facade 封装后最易被忽略的隐含契约。


















