Laravel 服務容器(Service Container)

Laravel 服務容器是一個強大的工具,用來管理類別依賴與實現依賴注入(Dependency Injection, DI)。簡單來說,就是將其他物件的依賴「注入」進來,而不是在類別內部自行 new


依賴注入(Dependency Injection, DI)

介紹

依賴注入是一種設計模式,讓類別依賴「抽象」而非具體的實作,能夠減少耦合、提升測試性與重用性。
被依賴的物件通常會透過建構子、方法參數或屬性被注入,而不是在類別內部直接建立。

使用時機

  • 使用介面導向程式設計時(如透過介面實作多型)
  • 建構子參數中需要其他服務(尤其是非 Laravel 可自動解析的)
  • 開發 Laravel 套件,並希望讓使用者可自定義替代服務實作

自動解析(Auto-Resolving)

Laravel 可以自動解析的情況

Laravel 能自動解析類別(無需手動綁定)條件如下:

  • 所有依賴都是具體類別(非介面、非 primitive)
  • 無建構子額外參數(如陣列、字串、整數等)
  • 無需單例
class ConfigService {
    public function getConfig() {
        return "載入設定...";
    }
}

class SimpleService {
    public function __construct(ConfigService $config) {
        $this->config = $config;
    }

    public function doSomething() {
        return $this->config->getConfig();
    }
}

class SimpleController extends Controller {
    public function __construct(SimpleService $service) {
        $this->service = $service;
    }

    public function index() {
        return $this->service->doSomething();
    }
}

即使多層依賴(A 依賴 B,B 依賴 C),Laravel 仍會自動解析:


必須手動綁定的情況

1. 建構子包含 Laravel 無法解析的類型(如 array、string)

class SimpleService {
    public function __construct(array $config) {
        $this->config = $config;
    }
}

$this->app->bind(SimpleService::class, function ($app) {
    return new SimpleService(['debug' => true]);
});

2. 依賴介面(Interface)

interface ConfigServiceInterface {
    public function getConfig();
}

class ConfigService implements ConfigServiceInterface {
    public function getConfig() {
        return "載入設定...";
    }
}

class SimpleService {
    public function __construct(ConfigServiceInterface $config) {
        $this->config = $config;
    }
}

$this->app->bind(ConfigServiceInterface::class, ConfigService::class);

3. 單例需求(Singleton)

$this->app->singleton(SimpleService::class, function ($app) {
    return new SimpleService($app->make(ConfigService::class));
});

綁定類型

bind(每次呼叫產生新的實例)

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

bind to closure(自定義建立邏輯)

$this->app->bind(PaymentGateway::class, function ($app) {
    return new StripePaymentGateway();
});

singleton(全域只產生一次)

$this->app->singleton(PaymentGateway::class, function ($app) {
    return new StripePaymentGateway();
});

instance(立即指定一個現有實例)

$gateway = new StripePaymentGateway();
$this->app->instance(PaymentGateway::class, $gateway);

Contextual Binding(基於上下文的綁定)

當有多個介面實作,並希望根據不同情境注入不同的實作時使用:

$this->app->when(StripePaymentController::class)
          ->needs(PaymentGateway::class)
          ->give(StripePaymentGateway::class);

$this->app->when(PayPalPaymentController::class)
          ->needs(PaymentGateway::class)
          ->give(PayPalPaymentGateway::class);

契約(Contracts)

契約介紹

Laravel 的 Contracts 是一組定義框架核心功能的介面。目的是讓開發者依賴「抽象」而不是具體實作,達到更高的可替換性、可測試性、與解耦。

Contract 範例

定義 Contract

// app/Contracts/PaymentGateway.php
namespace App\Contracts;

interface PaymentGateway {
    public function charge($amount);
}

實作 Contract

// app/Services/StripePaymentGateway.php
namespace App\Services;

use App\Contracts\PaymentGateway;

class StripePaymentGateway implements PaymentGateway {
    public function charge($amount) {
        return "Charged {$amount} using Stripe";
    }
}

綁定 Contract 到實作

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

在 Controller 中使用 Contract

use App\Contracts\PaymentGateway;

class PaymentController extends Controller {
    public function __construct(PaymentGateway $paymentGateway) {
        $this->paymentGateway = $paymentGateway;
    }

    public function charge() {
        return $this->paymentGateway->charge(100);
    }
}

單例模式(Singleton Pattern)

在某些情況下,不希望某個類別重複建立實例,可使用 Singleton Pattern。

PHP Singleton 實作

class Singleton {
    private static $instance;

    private function __construct() {}
    private function __clone() {}
    private function __wakeup() {}

    public static function getInstance() {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

$singleton = Singleton::getInstance();

app()->make() 建立實例

除了依賴注入外,也可以用 app()->make() 顯式建立服務:

public function store() {
    $request = app()->make(Request::class);
    $data = $request->all();
    // ...
}

小結

類型 說明
bind 每次呼叫產生新實例
singleton 全域單一實例,共用
instance 已建立好的實例注入
interface bind 將介面綁定至實作
contextual 特定類別使用特定實作
auto-resolve 沒有 interface/primitive 時 Laravel 可自動解析