pull()强制新建实例,不查$instances缓存,跳过单例复用,每次均反射构造、解析依赖并new对象,适用于需干净实例的场景如单元测试或多租户隔离。

pull 方法会跳过单例缓存直接新建实例
当你调用 Container::pull('UserService'),容器不会检查 $instances 中是否已有该类的单例,而是强制走一遍完整实例化流程:反射构造函数、解析依赖、new 实例、注入参数。即使你之前用 make() 创建过一次,pull() 仍会新建一个——这对需要“每次都是干净对象”的场景(比如单元测试中隔离状态、或临时处理不同上下文的请求)很关键。
常见错误现象是误以为 pull() 和 make() 行为一致,结果在循环里反复创建相同服务却没意识到内存和初始化开销在累积。
-
pull()不查$instances,只查$bind获取绑定目标 - 不触发
resolving()回调,但会触发invokeClass()的完整反射流程 - 适合需要“非共享实例”的场景,如多租户隔离、命令行任务中按批次重置状态
make 默认复用单例,且支持别名映射链
make() 是容器最常用的实例获取方式,它优先从 $instances 缓存中取已存在的单例;没有才查 $bind,再通过反射创建,并自动存入缓存。更重要的是,它会递归解析别名链:比如你绑定了 $container->bind('cache', 'think\Cache'),再 make('cache') 会先查出 think\Cache,然后继续 make('think\Cache') ——这层跳转对模块解耦很实用,但调试时容易卡在第二层找不到绑定而报错。
参数差异上,make($abstract, $vars = []) 支持传入构造参数数组,而 pull() 的第二个参数也是 $vars,但两者对参数的使用逻辑一致;区别只在是否绕过缓存。
立即学习“PHP免费学习笔记(深入)”;
- 第一次
make('db')→ 反射创建并存入$instances['think\Db'] - 第二次
make('db')→ 直接返回缓存实例,不走反射 - 若绑定的是闭包,
make()每次都执行闭包,但闭包本身可自行控制内部缓存
has() 和 getAlias() 对 pull / make 的行为无影响
has('xxx') 只判断 $bind 或 $instances 中是否存在键,不涉及实例化逻辑;getAlias('xxx') 单纯查 $bind 数组并返回映射值(或原样返回),也不触发任何创建动作。这两个方法常被误当作“预检”,但它们既不保证 make() 成功,也不影响 pull() 的强制新建行为。
容易踩的坑是:在中间件里先 has('auth') 返回 true,就以为 make('auth') 肯定能拿到实例——其实可能只是 $bind['auth'] = 'app\service\AuthService' 存在,但 app\service\AuthService 类文件根本没加载或命名空间写错,最终 make() 还是抛 ClassNotFoundException。
-
has()返回 true ≠ 类存在或可实例化 -
getAlias()返回字符串 ≠ 该字符串对应类已定义 - 真正验证可用性,得靠
try { $container->make($id); } catch (\Throwable $e) { ... }
TP6 开始 pull 已不推荐用于常规服务获取
TP6 容器严格遵循 PSR-11,make() 是标准接口方法,pull() 是 ThinkPHP 自定义扩展。如果你写的代码要兼顾框架升级或对接其他 PSR-11 兼容容器(比如 Laravel 的 Container),应避免依赖 pull()。官方文档也明确建议:仅在需要“非共享实例”时显式使用 pull(),其余一律走 make() 或依赖注入(如控制器方法参数类型提示)。
性能影响上,pull() 比 make() 多一次反射 + 构造函数调用,在高频调用路径(如日志记录器、格式化器)中会造成明显差异。更隐蔽的问题是:某些服务内部有静态状态或全局资源句柄(如 PDO 连接池管理器),pull() 新建实例可能破坏其预期生命周期。



















