跳到內容

服務容器

編輯此頁面

螢幕錄影

您偏好影片教學嗎?請查看 Symfony 基礎 screencast 系列

您的應用程式*充滿*了實用的物件:「Mailer」物件可能協助您發送電子郵件,而另一個物件可能協助您將內容儲存到資料庫。幾乎您應用程式「執行」的*每件事*實際上都是由這些物件之一完成的。而且每次您安裝新的套件,您都會獲得更多存取權!

在 Symfony 中,這些實用的物件稱為 服務 (services),每個服務都存在於一個非常特殊的物件中,稱為 服務容器 (service container)。容器讓您可以集中管理物件的建構方式。它讓您的生活更輕鬆,促進強大的架構,而且速度超快!

取得和使用服務

當您啟動 Symfony 應用程式時,您的容器*已經*包含許多服務。這些就像*工具*:等待您利用它們。在您的控制器中,您可以透過類型提示服務的類別或介面名稱來從容器「要求」服務。想要 記錄 (log) 某些內容嗎?沒問題!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Controller/ProductController.php
namespace App\Controller;

use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ProductController extends AbstractController
{
    #[Route('/products')]
    public function list(LoggerInterface $logger): Response
    {
        $logger->info('Look, I just used a service!');

        // ...
    }
}

還有哪些其他服務可用?執行以下指令來找出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ php bin/console debug:autowiring

  # this is just a *small* sample of the output...

  Autowirable Types
  =================

   The following classes & interfaces can be used as type-hints when autowiring:

   Describes a logger instance.
   Psr\Log\LoggerInterface - alias:logger

   Request stack that controls the lifecycle of requests.
   Symfony\Component\HttpFoundation\RequestStack - alias:request_stack

   RouterInterface is the interface that all Router classes must implement.
   Symfony\Component\Routing\RouterInterface - alias:router.default

   [...]

當您在控制器方法或您 自己的服務 內使用這些類型提示時,Symfony 將自動傳遞給您符合該類型的服務物件。

在整個文件中,您將看到如何使用容器中存在的許多不同服務。

提示

實際上,容器中還有*更多*服務,每個服務在容器中都有一個唯一的 ID,例如 request_stackrouter.default。如需完整列表,您可以執行 php bin/console debug:container。但大多數時候,您不需要擔心這個。請參閱 如何選擇特定的服務。請參閱 如何偵錯服務容器和列出服務

在容器中建立/設定服務

您也可以將*自己的*程式碼組織成服務。例如,假設您需要向使用者顯示隨機的、快樂的訊息。如果您將此程式碼放在您的控制器中,它就無法重複使用。相反地,您決定建立一個新的類別:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Service/MessageGenerator.php
namespace App\Service;

class MessageGenerator
{
    public function getHappyMessage(): string
    {
        $messages = [
            'You did it! You updated the system! Amazing!',
            'That was one of the coolest updates I\'ve seen all day!',
            'Great work! Keep going!',
        ];

        $index = array_rand($messages);

        return $messages[$index];
    }
}

恭喜!您已經建立了您的第一個服務類別!您可以立即在您的控制器中使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/Controller/ProductController.php
use App\Service\MessageGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ProductController extends AbstractController
{
    #[Route('/products/new')]
    public function new(MessageGenerator $messageGenerator): Response
    {
        // thanks to the type-hint, the container will instantiate a
        // new MessageGenerator and pass it to you!
        // ...

        $message = $messageGenerator->getHappyMessage();
        $this->addFlash('success', $message);
        // ...
    }
}

當您要求 MessageGenerator 服務時,容器會建構一個新的 MessageGenerator 物件並傳回它(請參閱下面的側邊欄)。但如果您從未要求該服務,它就*永遠*不會被建構:節省記憶體和速度。額外的好處是,MessageGenerator 服務只會被建立*一次*:每次您要求它時都會傳回相同的實例。

本文件假設您正在使用以下服務設定,這是新專案的預設設定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# config/services.yaml
services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    # order is important in this file because service definitions
    # always *replace* previous ones; add your own service configuration below

    # ...

提示

resourceexclude 選項的值可以是任何有效的 glob 模式exclude 選項的值也可以是 glob 模式的陣列。

由於此設定,您可以自動使用 src/ 目錄中的任何類別作為服務,而無需手動設定它。稍後,您將學習如何使用 resource 一次匯入多個服務

如果您偏好手動配置您的服務,您可以使用明確的設定

限制服務於特定的 Symfony 環境

您可以使用 #[When] 屬性僅在某些環境中註冊類別作為服務:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\DependencyInjection\Attribute\When;

// SomeClass is only registered in the "dev" environment

#[When(env: 'dev')]
class SomeClass
{
    // ...
}

// you can also apply more than one When attribute to the same class

#[When(env: 'dev')]
#[When(env: 'test')]
class AnotherClass
{
    // ...
}

如果您想排除在特定環境中註冊服務,您可以使用 #[WhenNot] 屬性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\DependencyInjection\Attribute\WhenNot;

// SomeClass is registered in all environments except "dev"

#[WhenNot(env: 'dev')]
class SomeClass
{
    // ...
}

// you can apply more than one WhenNot attribute to the same class

#[WhenNot(env: 'dev')]
#[WhenNot(env: 'test')]
class AnotherClass
{
    // ...
}

7.2

#[WhenNot] 屬性是在 Symfony 7.2 中引入的。

將服務/設定注入到服務中

如果您需要從 MessageGenerator 內部存取 logger 服務怎麼辦?沒問題!建立一個具有 LoggerInterface 類型提示的 $logger 參數的 __construct() 方法。將其設定在新的 $logger 屬性上,並稍後使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Service/MessageGenerator.php
namespace App\Service;

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    public function __construct(
        private LoggerInterface $logger,
    ) {
    }

    public function getHappyMessage(): string
    {
        $this->logger->info('About to find a happy message!');
        // ...
    }
}

就是這樣!容器將*自動*知道在實例化 MessageGenerator 時傳遞 logger 服務。它是如何知道這樣做的?自動裝配 (Autowiring)。關鍵在於您的 __construct() 方法中的 LoggerInterface 類型提示以及 services.yaml 中的 autowire: true 設定。當您類型提示參數時,容器將自動找到符合的服務。如果找不到,您將看到一個清晰的例外,並提供有用的建議。

順帶一提,將依賴項新增到您的 __construct() 方法的這種方法稱為 *依賴注入 (dependency injection)*。

您應該如何知道為類型提示使用 LoggerInterface?您可以閱讀您正在使用的任何功能的文檔,或者執行以下指令來取得可自動裝配的類型提示列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ php bin/console debug:autowiring

  # this is just a *small* sample of the output...

  Describes a logger instance.
  Psr\Log\LoggerInterface - alias:monolog.logger

  Request stack that controls the lifecycle of requests.
  Symfony\Component\HttpFoundation\RequestStack - alias:request_stack

  RouterInterface is the interface that all Router classes must implement.
  Symfony\Component\Routing\RouterInterface - alias:router.default

  [...]

處理多個服務

假設您也想在每次網站更新時透過電子郵件通知網站管理員。為此,您建立一個新的類別:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// src/Service/SiteUpdateManager.php
namespace App\Service;

use App\Service\MessageGenerator;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

class SiteUpdateManager
{
    public function __construct(
        private MessageGenerator $messageGenerator,
        private MailerInterface $mailer,
    ) {
    }

    public function notifyOfSiteUpdate(): bool
    {
        $happyMessage = $this->messageGenerator->getHappyMessage();

        $email = (new Email())
            ->from('admin@example.com')
            ->to('manager@example.com')
            ->subject('Site update just happened!')
            ->text('Someone just updated the site. We told them: '.$happyMessage);

        $this->mailer->send($email);

        // ...

        return true;
    }
}

這需要 MessageGenerator *和* Mailer 服務。沒問題,我們透過類型提示它們的類別和介面名稱來要求它們!現在,這個新的服務已準備好使用。例如,在控制器中,您可以類型提示新的 SiteUpdateManager 類別並使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Controller/SiteController.php
namespace App\Controller;

use App\Service\SiteUpdateManager;
// ...

class SiteController extends AbstractController
{
    public function new(SiteUpdateManager $siteUpdateManager): Response
    {
        // ...

        if ($siteUpdateManager->notifyOfSiteUpdate()) {
            $this->addFlash('success', 'Notification mail was sent successfully.');
        }

        // ...
    }
}

由於自動裝配和您在 __construct() 中的類型提示,容器會建立 SiteUpdateManager 物件並將正確的參數傳遞給它。在大多數情況下,這運作良好。

手動配置參數

但是,在少數情況下,服務的參數無法自動裝配。例如,假設您想要使管理員電子郵件可配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/Service/SiteUpdateManager.php
  // ...

  class SiteUpdateManager
  {
      // ...

      public function __construct(
          private MessageGenerator $messageGenerator,
          private MailerInterface $mailer,
+         private string $adminEmail
      ) {
      }

      public function notifyOfSiteUpdate(): bool
      {
          // ...

          $email = (new Email())
              // ...
-            ->to('manager@example.com')
+            ->to($this->adminEmail)
              // ...
          ;
          // ...
      }
  }

如果您進行此變更並重新整理,您將看到錯誤:

無法自動裝配服務 "App\Service\SiteUpdateManager":方法 "__construct()" 的參數 "$adminEmail" 必須具有類型提示或明確給定一個值。

這很有道理!容器無法知道您想在此處傳遞什麼值。沒問題!在您的設定中,您可以明確設定此參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
# config/services.yaml
services:
    # ... same as before

    # same as before
    App\:
        resource: '../src/'
        exclude: '../src/{DependencyInjection,Entity,Kernel.php}'

    # explicitly configure the service
    App\Service\SiteUpdateManager:
        arguments:
            $adminEmail: 'manager@example.com'

由於這樣做,當建立 SiteUpdateManager 服務時,容器將傳遞 manager@example.com__construct$adminEmail 參數。其他參數仍將自動裝配。

但是,這不是很脆弱嗎?幸運的是,不是!如果您將 $adminEmail 參數重新命名為其他名稱 - 例如 $mainEmail - 當您重新載入下一個頁面時,您將獲得一個清晰的例外(即使該頁面未使用此服務)。

服務參數

除了保存服務物件之外,容器還保存配置,稱為 參數 (parameters)。關於 Symfony 配置的主要文章詳細解釋了 配置參數,並顯示了它們的所有類型(字串、布林值、陣列、二進制和 PHP 常數參數)。

但是,還有另一種與服務相關的參數類型。在 YAML 配置中,任何以 @ 開頭的字串都被視為服務的 ID,而不是常規字串。在 XML 配置中,對參數使用 type="service" 類型,在 PHP 配置中使用 service() 函數:

1
2
3
4
5
6
7
8
9
10
11
# config/services.yaml
services:
    App\Service\MessageGenerator:
        arguments:
            # this is not a string, but a reference to a service called 'logger'
            - '@logger'

            # if the value of a string argument starts with '@', you need to escape
            # it by adding another '@' so Symfony doesn't consider it a service
            # the following example would be parsed as the string '@securepassword'
            # - '@@securepassword'

使用容器的參數存取方法來處理容器參數非常簡單:

1
2
3
4
5
6
7
8
// checks if a parameter is defined (parameter names are case-sensitive)
$container->hasParameter('mailer.transport');

// gets value of a parameter
$container->getParameter('mailer.transport');

// adds a new parameter
$container->setParameter('mailer.transport', 'sendmail');

警告

使用的 . 標記法是 Symfony 慣例,使參數更易於閱讀。參數是平面鍵值元素,它們不能組織成巢狀陣列。

注意

您只能在容器編譯之前設定參數,而不是在運行時。若要深入了解編譯容器,請參閱 編譯容器

選擇特定的服務

先前建立的 MessageGenerator 服務需要 LoggerInterface 參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Service/MessageGenerator.php
namespace App\Service;

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    public function __construct(
        private LoggerInterface $logger,
    ) {
    }
    // ...
}

但是,容器中有*多個*服務實現了 LoggerInterface,例如 loggermonolog.logger.requestmonolog.logger.php 等。容器如何知道要使用哪一個?

在這些情況下,容器通常配置為自動選擇其中一個服務 - 在這種情況下為 logger(在 自動定義服務依賴項(自動裝配) 中閱讀更多關於原因的資訊)。但是,您可以控制此行為並傳入不同的 logger:

1
2
3
4
5
6
7
8
9
10
11
# config/services.yaml
services:
    # ... same code as before

    # explicitly configure the service
    App\Service\MessageGenerator:
        arguments:
            # the '@' symbol is important: that's what tells the container
            # you want to pass the *service* whose id is 'monolog.logger.request',
            # and not just the *string* 'monolog.logger.request'
            $logger: '@monolog.logger.request'

這告訴容器,__construct$logger 參數應使用 ID 為 monolog.logger.request 的服務。

如需可用於自動裝配的 logger 服務列表,請執行:

1
$ php bin/console debug:autowiring logger

如需容器中*所有*可能服務的完整列表,請執行:

1
$ php bin/console debug:container

移除服務

如果需要,可以從服務容器中移除服務。例如,這對於使服務在某些 配置環境 中不可用很有用(例如在 test 環境中):

1
2
3
4
5
6
7
8
9
10
// config/services_test.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use App\RemovedService;

return function(ContainerConfigurator $containerConfigurator) {
    $services = $containerConfigurator->services();

    $services->remove(RemovedService::class);
};

現在,容器在 test 環境中將不包含 App\RemovedService

注入 Closure 作為參數

可以將可呼叫物件 (callable) 作為服務的參數注入。讓我們為我們的 MessageGenerator 建構子新增一個參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/Service/MessageGenerator.php
namespace App\Service;

use Psr\Log\LoggerInterface;

class MessageGenerator
{
    private string $messageHash;

    public function __construct(
        private LoggerInterface $logger,
        callable $generateMessageHash,
    ) {
        $this->messageHash = $generateMessageHash();
    }
    // ...
}

現在,我們將新增一個新的可調用服務來產生訊息雜湊:

1
2
3
4
5
6
7
8
9
10
// src/Hash/MessageHashGenerator.php
namespace App\Hash;

class MessageHashGenerator
{
    public function __invoke(): string
    {
        // Compute and return a message hash
    }
}

我們的配置看起來像這樣:

1
2
3
4
5
6
7
8
9
# config/services.yaml
services:
    # ... same code as before

    # explicitly configure the service
    App\Service\MessageGenerator:
        arguments:
            $logger: '@monolog.logger.request'
            $generateMessageHash: !closure '@App\Hash\MessageHashGenerator'

另請參閱

可以使用 自動裝配 及其專用屬性來注入 Closure。

依名稱或類型綁定參數

您也可以使用 bind 關鍵字依名稱或類型綁定特定參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# config/services.yaml
services:
    _defaults:
        bind:
            # pass this value to any $adminEmail argument for any service
            # that's defined in this file (including controller arguments)
            $adminEmail: 'manager@example.com'

            # pass this service to any $requestLogger argument for any
            # service that's defined in this file
            $requestLogger: '@monolog.logger.request'

            # pass this service for any LoggerInterface type-hint for any
            # service that's defined in this file
            Psr\Log\LoggerInterface: '@monolog.logger.request'

            # optionally you can define both the name and type of the argument to match
            string $adminEmail: 'manager@example.com'
            Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request'
            iterable $rules: !tagged_iterator app.foo.rule

    # ...

透過將 bind 鍵放在 _defaults 下,您可以為在此檔案中定義的*任何*服務指定*任何*參數的值!您可以依名稱(例如 $adminEmail)、依類型(例如 Psr\Log\LoggerInterface)或兩者(例如 Psr\Log\LoggerInterface $requestLogger)綁定參數。

bind 配置也可以應用於特定服務或在 一次載入多個服務 時使用。

抽象服務參數

有時,某些服務參數的值無法在配置文件中定義,因為它們是在運行時使用 編譯器傳遞 (compiler pass)套件擴充 (bundle extension) 計算出來的。

在這些情況下,您可以使用 abstract 參數類型來至少定義參數的名稱以及關於其用途的一些簡短描述:

1
2
3
4
5
6
7
8
9
# config/services.yaml
services:
    # ...

    App\Service\MyService:
        arguments:
            $rootNamespace: !abstract 'should be defined by Pass'

    # ...

如果您在運行時未替換抽象參數的值,則會拋出 RuntimeException,並顯示類似 Argument "$rootNamespace" of service "App\Service\MyService" is abstract: should be defined by Pass. 的訊息。

autowire 選項

在上面,services.yaml 檔案在 _defaults 區段中具有 autowire: true,以便它適用於在該檔案中定義的所有服務。透過此設定,您可以在服務的 __construct() 方法中類型提示參數,容器將自動傳遞給您正確的參數。整個條目都是圍繞自動裝配編寫的。

有關自動裝配的更多詳細資訊,請查看 自動定義服務依賴項(自動裝配)

autoconfigure 選項

在上面,services.yaml 檔案在 _defaults 區段中具有 autoconfigure: true,以便它適用於在該檔案中定義的所有服務。透過此設定,容器將根據服務的類別自動將某些配置應用於您的服務。這主要用於自動標記 (auto-tag) 您的服務。

例如,若要建立 Twig 擴充功能,您需要建立一個類別,將其註冊為服務,並使用 twig.extension 標記 (tag) 它。

但是,若使用 autoconfigure: true,您就不需要標籤。實際上,如果您使用預設的 services.yaml 設定,您甚至完全不需要做任何事:服務將會被自動載入。接著,autoconfigure您新增 twig.extension 標籤,因為您的類別實作了 Twig\Extension\ExtensionInterface。而且感謝 autowire,您甚至可以新增建構子參數,而無需任何設定。

自動設定也適用於屬性。有些屬性,例如 AsMessageHandlerAsEventListenerAsCommand,都已註冊用於自動設定。任何使用這些屬性的類別都會套用標籤。

檢查服務定義

lint:container 命令會執行額外的檢查,以確保容器已正確設定。在將應用程式部署到生產環境之前(例如,在您的持續整合伺服器中)執行此命令非常有用。

1
2
3
4
5
$ php bin/console lint:container

# optionally, you can force the resolution of environment variables;
# the command will fail if any of those environment variables are missing
$ php bin/console lint:container --resolve-env-vars

7.2

--resolve-env-vars 選項是在 Symfony 7.2 中引入的。

在每次編譯容器時執行這些檢查可能會降低效能。這就是為什麼它們在名為 CheckTypeDeclarationsPassCheckAliasValidityPass編譯器傳遞中實作,這些傳遞預設為停用,僅在執行 lint:container 命令時啟用。如果您不介意效能損失,可以在您的應用程式中啟用這些編譯器傳遞。

7.1

CheckAliasValidityPass 編譯器傳遞是在 Symfony 7.1 中引入的。

公開與私有服務

預設情況下,每個定義的服務都是私有的。當服務是私有的,您就無法使用 $container->get() 從容器直接存取它。作為最佳實務,您應該只建立私有服務,並且應該使用依賴注入來取得服務,而不是使用 $container->get()

如果您需要延遲取得服務,您應該考慮使用 服務定位器,而不是使用公有服務。

但是,如果您確實需要將服務設為公有,請覆寫 public 設定

1
2
3
4
5
6
7
# config/services.yaml
services:
    # ... same code as before

    # explicitly configure the service
    App\Service\PublicService:
        public: true

也可以藉由 #[Autoconfigure] 屬性將服務定義為公有。此屬性必須直接用於您想要設定的服務類別上

1
2
3
4
5
6
7
8
9
10
// src/Service/PublicService.php
namespace App\Service;

use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure(public: true)]
class PublicService
{
    // ...
}

使用 resource 一次匯入多個服務

您已經看過,您可以使用 resource 鍵一次匯入多個服務。例如,預設的 Symfony 設定包含這個

1
2
3
4
5
6
7
8
9
# config/services.yaml
services:
    # ... same as before

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude: '../src/{DependencyInjection,Entity,Kernel.php}'

提示

resourceexclude 選項的值可以是任何有效的 glob 模式。如果您只想排除少數服務,您可以使用 Exclude 屬性直接在您的類別上排除它。

這可以用於快速地將許多類別作為服務提供,並套用一些預設設定。每個服務的 id 都是其完全限定的類別名稱。您可以藉由使用其 ID(類別名稱)在下方覆寫任何已匯入的服務(例如,請參閱如何手動連接引數)。如果您覆寫服務,則不會從匯入繼承任何選項(例如 public),但覆寫的服務仍然會從 _defaults 繼承。

您也可以 exclude 某些路徑。這是可選的,但會稍微提高 dev 環境中的效能:排除的路徑不會被追蹤,因此修改它們不會導致容器重建。

注意

等等,這是否表示 src/ 中的每個類別都會註冊為服務?即使是模型類別?實際上,並非如此。只要您將匯入的服務保持為私有src/ 中所有明確用作服務的類別都會自動從最終容器中移除。實際上,匯入意味著所有類別都「可以用作服務」,而無需手動設定。

使用相同命名空間的多個服務定義

如果您使用 YAML 設定格式定義服務,則 PHP 命名空間會用作每個設定的鍵,因此您無法為同一命名空間下的類別定義不同的服務設定

1
2
3
4
5
# config/services.yaml
services:
    App\Domain\:
        resource: '../src/Domain/*'
        # ...

為了擁有多個定義,請新增 namespace 選項,並使用任何唯一字串作為每個服務設定的鍵

1
2
3
4
5
6
7
8
9
10
11
# config/services.yaml
services:
    command_handlers:
        namespace: App\Domain\
        resource: '../src/Domain/*/CommandHandler'
        tags: [command_handler]

    event_subscribers:
        namespace: App\Domain\
        resource: '../src/Domain/*/EventSubscriber'
        tags: [event_subscriber]

明確地設定服務和參數

自動載入服務自動連線是可選的。即使您使用它們,在某些情況下,您可能仍想要手動連線服務。例如,假設您想要為 SiteUpdateManager 類別註冊2個服務 - 每個服務都有不同的管理員電子郵件。在這種情況下,每個服務都需要有唯一的服務 ID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# config/services.yaml
services:
    # ...

    # this is the service's id
    site_update_manager.superadmin:
        class: App\Service\SiteUpdateManager
        # you CAN still use autowiring: we just want to show what it looks like without
        autowire: false
        # manually wire all arguments
        arguments:
            - '@App\Service\MessageGenerator'
            - '@mailer'
            - 'superadmin@example.com'

    site_update_manager.normal_users:
        class: App\Service\SiteUpdateManager
        autowire: false
        arguments:
            - '@App\Service\MessageGenerator'
            - '@mailer'
            - 'contact@example.com'

    # Create an alias, so that - by default - if you type-hint SiteUpdateManager,
    # the site_update_manager.superadmin will be used
    App\Service\SiteUpdateManager: '@site_update_manager.superadmin'

在這種情況下,註冊了兩個服務:site_update_manager.superadminsite_update_manager.normal_users。感謝別名,如果您類型提示 SiteUpdateManager,則第一個 (site_update_manager.superadmin) 將會被傳遞。

如果您想要傳遞第二個,您需要手動連線服務或建立具名的 自動連線別名

警告

如果您沒有建立別名,並且從 src/ 載入所有服務,那麼就已經建立了三個服務(自動服務 + 您的兩個服務),並且當您類型提示 SiteUpdateManager 時,預設情況下將會傳遞自動載入的服務。這就是為什麼建立別名是個好主意。

當使用 PHP 閉包來設定您的服務時,可以透過在閉包中新增名為 $env 的字串引數,來自動注入目前的環境值

1
2
3
4
5
6
7
// config/packages/my_config.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return function(ContainerConfigurator $containerConfigurator, string $env): void {
    // `$env` is automatically filled in, so you can configure your
    // services depending on which environment you're on
};

為函數介面產生适配器

函數介面是具有單一方法的介面。它們在概念上與閉包非常相似,只是它們的唯一方法具有名稱。此外,它們可以用作程式碼中的類型提示。

AutowireCallable 屬性可用於為函數介面產生一個適配器。假設您有以下函數介面

1
2
3
4
5
6
7
// src/Service/MessageFormatterInterface.php
namespace App\Service;

interface MessageFormatterInterface
{
    public function format(string $message, array $parameters): string;
}

您也有一個服務定義了許多方法,其中一個方法與先前介面的 format() 方法相同

1
2
3
4
5
6
7
8
9
10
11
12
// src/Service/MessageUtils.php
namespace App\Service;

class MessageUtils
{
    // other methods...

    public function format(string $message, array $parameters): string
    {
        // ...
    }
}

感謝 #[AutowireCallable] 屬性,您現在可以將此 MessageUtils 服務作為函數介面實作注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace App\Service\Mail;

use App\Service\MessageFormatterInterface;
use App\Service\MessageUtils;
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;

class Mailer
{
    public function __construct(
        #[AutowireCallable(service: MessageUtils::class, method: 'format')]
        private MessageFormatterInterface $formatter
    ) {
    }

    public function sendMail(string $message, array $parameters): string
    {
        $formattedMessage = $this->formatter->format($message, $parameters);

        // ...
    }
}

除了使用 #[AutowireCallable] 屬性之外,您也可以透過設定為函數介面產生適配器

1
2
3
4
5
6
7
8
# config/services.yaml
services:

    # ...

    app.message_formatter:
        class: App\Service\MessageFormatterInterface
        from_callable: [!service {class: 'App\Service\MessageUtils'}, 'format']

這樣做,Symfony 將產生一個類別(也稱為適配器),實作 MessageFormatterInterface,它會將 MessageFormatterInterface::format() 的呼叫轉發到您底層服務的方法 MessageUtils::format(),並帶有其所有引數。

本作品,包括程式碼範例,依 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本