不能直接 new,否则 Service 脱离容器管理,导致依赖无法注入、事务失控、测试不可 mock;应优先用方法参数注入,其次 app()->make(),或启用容器接管控制器后使用构造函数注入。

不要直接 new。Service 类一旦用 new 实例化,就彻底脱离容器管理,依赖无法注入、事务无法统一控制、单元测试无法 mock,等于把 Service 目录当普通工具类放着用——结构有了,解耦没实现。
为什么 new 之后依赖注入失效
ThinkPHP 的依赖注入(DI)只在容器通过反射创建实例时触发。控制器里写 public function __construct(UserService $service),前提是框架必须用容器去实例化这个控制器;而 new UserService() 是纯 PHP 原生操作,绕过所有容器逻辑,$service 参数根本不会被解析,更不会被传入。
- 类文件存在、命名空间正确、
composer dump-autoload已执行 —— 这些都白搭,new不走容器,反射压根不启动 - 如果 Service 构造函数里有
OrderRepository $repo这类依赖,new UserService()会直接报错:参数缺失或类型不匹配 - 即使 Service 没有依赖,硬 new 出来的实例也无法享受容器提供的单例、生命周期管理、AOP 织入等能力
应该用什么方式获取 Service 实例
三种可用方式,按推荐顺序排列:
- 方法参数注入(最轻量、零配置):
public function index(UserService $service),路由层自动解析并从容器拉取,连provider.php都不用改 - 容器显式获取:
app()->make(UserService::class),适合在非控制器上下文(如命令行、事件监听器、中间件)中使用 - 构造函数注入(需启用容器接管控制器):在
app/provider.php的providers数组里加上控制器全名,例如app\controller\Index::class,否则构造函数永远不执行
new 的典型误用场景和后果
常见于“快速验证逻辑”或“历史代码迁移”,但代价隐蔽:
立即学习“PHP免费学习笔记(深入)”;
- 事务失效:Service 方法里用了
Db::transaction(),但调用方是new出来的,上层没开启事务上下文,回滚不了 - 日志/监控丢失:Service 依赖了
LogService或Tracer,new后这些依赖为null,静默失败 - 环境隔离破坏:本地开发用 MockService,生产用 RealService,靠容器绑定切换;
new写死具体类,环境配置失效 - Redis 连接复用失效:如果 Service 封装了 Redis 操作,
new每次都新建连接句柄,高并发下容易打满连接池
真正难的不是写 Service,而是让每个调用点都走容器——哪怕只是一个 app()->make(),也得确保类已注册命名空间、composer dump-autoload 已运行、provider.php 里没漏掉绑定。漏掉任一环,new 就成了最省事也最危险的“补丁”。



















