2.2 服务容器与依赖注入:物资调度中心


2.2 服务容器与依赖注入:物资调度中心

本节摘要:服务容器是 Laravel 的物资调度中心:类需要的工具不自己造,报名字从容器领。本节讲清三种绑定方式(bind、singleton、接口绑定)、零配置的自动解析原理,以及服务提供者如何在框架启动前把物资登记进场,最后拆开门面(Facade)静态调用背后的动态代理。读完你能亲手完成一次"换实现不改业务代码"的演练。

为什么需要一台调度机器

上一节的例子里,控制器没有 new 任何东西就拿到了 Request。如果不要容器,代码会长这样:每个类自己 new 依赖,依赖又有依赖,改一个构造函数签名全场跟着改。容器把"找工具"这件事集中收编:工具只登记一次,全工地按需领取。这就是依赖注入(DI)——依赖从外面送进来,而不是类里自己生出来。

容器的三种绑定方式

把容器想成仓库登记处,入库有三种登记法:

<?php use App\Services\Payment\AlipayGateway; use App\Services\Payment\PaymentGateway; use App\Services\Payment\WechatGateway; use Illuminate\Contracts\Foundation\Application; // 一、普通绑定 bind:每次领料都造新的 $this->app->bind(PaymentGateway::class, AlipayGateway::class); // 二、单例绑定 singleton:全场共用一份 $this->app->singleton(ReportBuilder::class); // 三、按场景绑定:订单模块走支付宝,退款模块走微信 $this->app->bind(PaymentGateway::class, function (Application $app) { return $app->make(AlipayGateway::class); });

第三种场景更常见的需求是"同一接口,不同工位给不同货",用 contextual binding:

<?php // 订单控制器要支付宝,退款控制器要微信,同一个接口两种货 $this->app->when(OrderController::class) ->needs(PaymentGateway::class) ->give(AlipayGateway::class); $this->app->when(RefundController::class) ->needs(PaymentGateway::class) ->give(WechatGateway::class);

自动解析:为什么什么都不绑也能用

容器拿到你想要的类型后,会用 PHP 反射读构造函数签名,缺什么就递归去造什么。所以纯 PHP 类、框架组件,大多不需要显式绑定——报上类型名即可:

<?php namespace App\Http\Controllers; use App\Services\ReportBuilder; use Illuminate\Http\Request; class ReportController extends Controller { // 构造器注入:适合本类处处要用的依赖 public function __construct(private ReportBuilder $builder) { } // 方法注入:只在这个动作里要用的依赖 public function monthly(Request $request, ReportBuilder $builder) { return $builder->build($request->user()); } }

⚠️ 常见坑:自动解析只能搞定"类型明确"的依赖。构造参数是接口而容器不知道该给哪个实现、或是 int 和 string 这类标量时,解析会直接报错——前者用接口绑定登记,后者改为从 Request 里取。

服务提供者:工地的进场登记处

绑定代码写在哪?写进服务提供者(Service Provider)。它在框架启动时被拉起,register 阶段只做登记不使用服务(因为别人可能还没注册完),boot 阶段所有登记都完成了,可以做视图合成器、事件监听这类跨模块装配。Laravel 11 下 artisan make:provider 建好后,框架会自动发现并注册它。

<?php namespace App\Providers; use App\Services\Payment\AlipayGateway; use App\Services\Payment\PaymentGateway; use Illuminate\Support\ServiceProvider; class PaymentServiceProvider extends ServiceProvider { // 登记阶段:只往仓库里写登记条目 public function register(): void { $this->app->bind(PaymentGateway::class, AlipayGateway::class); } // 启动阶段:全部登记完成,可以安全使用服务 public function boot(): void { // } }

图 2-2:容器调度与进场登记时序

图 2-2:容器调度与进场登记时序

门面:给仓库加一部内线电话

门面(Facade)让你用 Route::get()、Cache::get() 这种静态写法,实际却走容器解析。原理一句话:门面基类定义了 __callStatic 魔术方法,把静态调用翻译成"从容器取出同名服务再调它"。它不是静态类,是动态代理。

<?php // 自定义门面的三步 namespace App\Facades; use Illuminate\Support\Facades\Facade; // 第一步 声明门面类,指明它代理容器里的哪个服务 class ReleaseNote extends Facade { protected static function getFacadeAccessor(): string { return \App\Services\ReleaseNoteService::class; } } // 使用侧:看起来像静态调用,实际每次都从容器解析 // ReleaseNote::publish($order);

工程取舍上我的倾向:同一个类内部用构造器注入,模板或极简场景用门面,不要全项目门面满天飞——门面把依赖藏起来了,测试时要专门伪造(fake),依赖显式写进构造器才是自明的。

本节要点回顾

  • 三种登记法:bind 每次新造、singleton 全场一份、when 按工位给货,覆盖了替换实现的全部场景。
  • 自动解析:反射加递归,类型明确就零配置;接口与标量是它的盲区,需要显式登记。
  • 提供者两阶段:register 只登记、boot 再装配,顺序不能颠倒。
  • 门面本质:__callStatic 加容器解析的动态代理,好用但别滥用。

理解了物资调度,下一节把整条流水线跑起来:一个请求从 public 目录进门,到响应出去,在内核里完整走一遍。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U