跳转到主要内容

什么是服务容器

Laravel 的服务容器是用来管理类依赖并进行依赖注入的机制。所谓依赖注入,指的是通过构造器或(在某些情况下)setter 方法,将类所需的依赖「注入」到类中。 来看下面的示例:
<?php

namespace App\Http\Controllers;

use App\Services\AppleMusic;
use Illuminate\View\View;

class PodcastController extends Controller
{
    /**
     * 创建新的控制器实例
     */
    public function __construct(
        protected AppleMusic $apple,
    ) {}

    /**
     * 显示指定 podcast 的信息
     */
    public function show(string $id): View
    {
        return view('podcasts.show', [
            'podcast' => $this->apple->findPodcast($id)
        ]);
    }
}
此示例中,PodcastController 需要从 Apple Music 等数据源获取 Podcast。因此我们将能够获取 Podcast 的服务注入给它。这样一来,在测试时就可以很方便地替换 AppleMusic 服务的 mock(假实现)。
深入理解服务容器对于构建大型 Laravel 应用不可或缺,也有助于为 Laravel 核心贡献代码。

零配置解析

如果类只依赖其他具体类(而不是接口),就无需告诉容器如何解析它。例如你在 routes/web.php 中写下这段代码:
<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    dd($service::class);
});
这个例子把类定义在路由文件内只是为了演示。真实应用中,服务类请放到 app/Services 目录下。
访问该路由时,Laravel 会自动解析 Service 类并注入到路由处理器中。你无需任何配置就能享受依赖注入。 Laravel 应用中编写的许多类(控制器、事件监听器、中间件等)都会通过容器自动注入依赖。

绑定

基本绑定

大部分绑定都会在服务提供者中注册。在服务提供者中可以通过 $this->app 属性访问容器。

bind

bind 方法把类或接口名与一个闭包组合,注册为绑定。
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
闭包会接收容器本身作为参数,可以用它解析子依赖。 如果想在服务提供者之外操作容器,请使用 App 门面。
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});
不依赖接口的类无需向容器注册绑定。容器可以通过反射自动解析这类对象。

singleton

singleton 方法会让某个类或接口只解析一次。解析出来的单例,后续对容器的调用都会返回同一实例。
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->singleton(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

instance

也可以通过 instance 方法把已有的对象实例绑定到容器上。之后对该绑定的解析都会返回该实例。
use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

$this->app->instance(Transistor::class, $service);

将接口绑定到实现

服务容器的一个强大功能是可以把接口绑定到特定实现。以 EventPusher 接口与 RedisEventPusher 实现为例:
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);
之后,容器就会给需要 EventPusher 实现的类注入 RedisEventPusher。你只需在构造器中类型提示 EventPusher 接口即可。
use App\Contracts\EventPusher;

/**
 * 创建新的类实例
 */
public function __construct(
    protected EventPusher $pusher,
) {}
依赖接口后,即使替换实现也无需修改代码,测试和未来的更改都更方便。

自动解析(通过类型提示进行 DI)

服务容器在解析控制器、事件监听器、中间件等类时,会读取构造器的类型提示并自动注入依赖。
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * 创建新的控制器实例
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}
只要 UserRepository 不依赖接口,就无需向容器注册。当访问对应路由时,容器会自动解析依赖并注入控制器。

从容器解析

make 方法

使用 make 方法可以从容器中解析类实例。
use App\Services\Transistor;

$transistor = app()->make(Transistor::class);
若某个依赖无法被容器解析,可以使用 makeWith 方法额外传入参数。
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

自动注入

实际开发中很少直接调用 make。只要在容器负责解析的类(控制器、事件监听器、中间件等)的构造器上加上类型提示,容器就会自动注入依赖。

门面与容器的关系

Laravel 的门面为容器中的对象提供了静态接口。例如,Cache::get() 在内部会从容器获取 Cache 服务并调用。
use Illuminate\Support\Facades\Cache;

// 通过门面调用
Cache::get('key');

// 直接使用容器的等价调用
app('cache')->get('key');
门面是容器的便捷包装。测试时也可以把门面替换成 mock。
use Illuminate\Support\Facades\Cache;

Cache::shouldReceive('get')
    ->once()
    ->with('key')
    ->andReturn('value');

构造器注入实战示例

来看实际应用中一种典型模式。
1

定义接口

<?php

namespace App\Contracts;

interface PaymentGateway
{
    public function charge(int $amount, string $token): bool;
}
2

创建实现类

<?php

namespace App\Services;

use App\Contracts\PaymentGateway;

class StripePaymentGateway implements PaymentGateway
{
    public function charge(int $amount, string $token): bool
    {
        // 使用 Stripe API 进行扣款...
        return true;
    }
}
3

在服务提供者中绑定

use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;

$this->app->singleton(PaymentGateway::class, StripePaymentGateway::class);
4

在控制器中接收注入

<?php

namespace App\Http\Controllers;

use App\Contracts\PaymentGateway;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function __construct(
        protected PaymentGateway $payment,
    ) {}

    public function store(Request $request)
    {
        $this->payment->charge(
            $request->amount,
            $request->payment_token
        );

        // ...
    }
}
采用这种模式后,即使把支付服务从 Stripe 换成其他供应商,也只需修改一处绑定。

下一步

服务提供者

学习如何通过服务提供者注册绑定。
最后修改于 2026年7月13日