本节摘要:服务容器是 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 { // } }

门面(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),依赖显式写进构造器才是自明的。
理解了物资调度,下一节把整条流水线跑起来:一个请求从 public 目录进门,到响应出去,在内核里完整走一遍。