服務容器
螢幕錄影
您偏好影片教學嗎?請查看 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_stack
或 router.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
服務只會被建立*一次*:每次您要求它時都會傳回相同的實例。
限制服務於特定的 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
,例如 logger
、monolog.logger.request
、monolog.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
,您甚至可以新增建構子參數,而無需任何設定。
自動設定也適用於屬性。有些屬性,例如 AsMessageHandler、AsEventListener 和 AsCommand,都已註冊用於自動設定。任何使用這些屬性的類別都會套用標籤。
檢查服務定義
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 中引入的。
在每次編譯容器時執行這些檢查可能會降低效能。這就是為什麼它們在名為 CheckTypeDeclarationsPass
和 CheckAliasValidityPass
的編譯器傳遞中實作,這些傳遞預設為停用,僅在執行 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}'
這可以用於快速地將許多類別作為服務提供,並套用一些預設設定。每個服務的 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.superadmin
和 site_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()
,並帶有其所有引數。