门面调用think\facade\request是静态代理,每次调用如request::param()都通过__callstatic转发给容器中单例的think\request(或app\request)实例,不保存状态、不可mock、ide无提示;而依赖注入public function index(request $request)获取的是同一真实对象引用,支持类型提示、单元测试和复用,工程性更强。

门面调用 think\facade\Request 是静态代理,不是真实对象实例
你写 Request::param('name') 看起来像在调用静态方法,其实底层每次都会从容器中获取一个 think\Request 实例,再转发调用。它不保存状态,也不支持复用——每次调用都相当于重新 app()->make(Request::class)。
常见错误现象:在循环里反复用 Request::get() 取参数,误以为能缓存或共享请求上下文;实际每次都是新实例,但开销微乎其微(TP6 默认启用单例绑定,所以容器返回的是同一个实例)。
- 门面类本身无业务逻辑,只做方法转发
- 不能被 Mock 用于单元测试(静态方法无法被替换)
- IDE 提示弱,因为
Request::xxx()的调用链是运行时解析的 - 若未正确绑定
think\Request到容器,会直接报错Facade class not found
依赖注入 think\Request 是类型提示 + 容器自动解析
控制器方法签名里写 public function index(Request $request),框架会在执行前从容器取一个 think\Request 实例传进来。这个 $request 是真实对象引用,可赋值给属性、传入其他服务、甚至被 PHPUnit Mock。
使用场景:需要在多个方法间复用同一请求上下文,或要对请求行为做测试隔离时必须用依赖注入。
- 构造函数注入也有效:
public function __construct(Request $request) { $this->request = $request; } - 必须
use think\Request,不能use think\facade\Request(否则类型提示失败) - 如果控制器继承了基类(如
BaseController),且基类已注入$this->request,那子类直接用$this->request即可,本质仍是依赖注入的延伸 - TP6 默认把
think\Request绑定为单例,所以注入的多个$request实际指向同一对象
性能和兼容性几乎没差别,但可测试性差很远
两者底层都走容器 app()->get(Request::class),启动阶段已注册为单例,所以运行时性能差异可以忽略。真正拉开差距的是工程能力。
容易踩的坑:你在门面调用里加断点,发现 Request::instance() 返回的对象和依赖注入进来的 $request 居然 === 相等——这不是巧合,是 TP6 容器默认单例策略的结果;但别因此觉得门面“更轻量”,它只是隐藏了对象生命周期。
- 写单元测试时,
Request::mock()是门面提供的临时方案,但只能全局生效,无法按测试用例隔离 - 依赖注入配合接口(如自定义
RequestInterface)才能实现真正的解耦,门面完全绕不开具体类 - 升级到 ThinkPHP 8 后,门面类默认不再自动加载,需显式
use,而依赖注入因类型提示存在,编译期就能发现缺失
什么时候该选哪个?看是否需要控制权
如果你只是快速取个 param 或判断 isPost,用门面最省事;但只要涉及重构、测试、或要把请求逻辑抽成独立 service,就必须用依赖注入。
一个常被忽略的细节:TP6 的 app\Request 是继承自 think\Request 的自定义类,如果你重写了 param() 方法,门面调用 Request::param() 会走你重写的逻辑(因为门面代理的是容器里的绑定类),但依赖注入时若类型提示写的是 think\Request,就可能绕过你的子类——必须提示为 app\Request 才行。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











