DependencyInjection 組件
DependencyInjection 組件實作了 PSR-11 相容的服務容器,讓您可以標準化和集中化應用程式中物件的建構方式。
如需依賴注入和服務容器的簡介,請參閱服務容器。
安裝
1
$ composer require symfony/dependency-injection
注意
如果您在 Symfony 應用程式之外安裝此組件,您必須在程式碼中引入 vendor/autoload.php
檔案,以啟用 Composer 提供的類別自動載入機制。請閱讀這篇文章以取得更多詳細資訊。
基本用法
另請參閱
本文說明如何在任何 PHP 應用程式中將 DependencyInjection 功能作為獨立組件使用。請閱讀服務容器文章,以了解如何在 Symfony 應用程式中使用它。
您可能有一個像下面這樣的 Mailer
類別,您想要將其作為服務提供。
1 2 3 4 5 6 7 8 9 10 11
class Mailer
{
private string $transport;
public function __construct()
{
$this->transport = 'sendmail';
}
// ...
}
您可以將其註冊到容器中作為服務
1 2 3 4
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container->register('mailer', 'Mailer');
為了使類別更具彈性,一個改進方法是允許容器設定使用的 transport
。如果您更改類別,使其傳遞到建構子中:
1 2 3 4 5 6 7 8 9
class Mailer
{
public function __construct(
private string $transport,
) {
}
// ...
}
那麼您可以在容器中設定傳輸方式的選擇:
1 2 3 4 5 6
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container
->register('mailer', 'Mailer')
->addArgument('sendmail');
這個類別現在更具彈性,因為您已將傳輸方式的選擇從實作中分離出來,放入容器中。
您選擇的郵件傳輸方式可能是其他服務需要知道的資訊。您可以將其設為容器中的參數,然後在 Mailer
服務的建構子引數中參考此參數,以避免在多個地方進行更改:
1 2 3 4 5 6 7
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
$container->setParameter('mailer.transport', 'sendmail');
$container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');
現在 mailer
服務已在容器中,您可以將其作為其他類別的依賴項注入。如果您有一個像這樣的 NewsletterManager
類別:
1 2 3 4 5 6 7 8 9
class NewsletterManager
{
public function __construct(
private \Mailer $mailer,
) {
}
// ...
}
在定義 newsletter_manager
服務時,mailer
服務尚未存在。使用 Reference
類別來告知容器在初始化 newsletter manager 時注入 mailer
服務:
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
$container = new ContainerBuilder();
$container->setParameter('mailer.transport', 'sendmail');
$container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');
$container
->register('newsletter_manager', 'NewsletterManager')
->addArgument(new Reference('mailer'));
如果 NewsletterManager
不需要 Mailer
,並且注入它是可選的,那麼您可以改用 setter 注入:
1 2 3 4 5 6 7 8 9 10 11
class NewsletterManager
{
private \Mailer $mailer;
public function setMailer(\Mailer $mailer): void
{
$this->mailer = $mailer;
}
// ...
}
現在您可以選擇不將 Mailer
注入到 NewsletterManager
中。如果您確實想要注入,那麼容器可以呼叫 setter 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
$container = new ContainerBuilder();
$container->setParameter('mailer.transport', 'sendmail');
$container
->register('mailer', 'Mailer')
->addArgument('%mailer.transport%');
$container
->register('newsletter_manager', 'NewsletterManager')
->addMethodCall('setMailer', [new Reference('mailer')]);
然後,您可以像這樣從容器中取得您的 newsletter_manager
服務:
1 2 3 4 5 6 7
use Symfony\Component\DependencyInjection\ContainerBuilder;
$container = new ContainerBuilder();
// ...
$newsletterManager = $container->get('newsletter_manager');
取得不存在的服務
預設情況下,當您嘗試取得不存在的服務時,會看到例外。您可以按如下方式覆寫此行為:
1 2 3 4 5 6 7 8 9
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
$containerBuilder = new ContainerBuilder();
// ...
// the second argument is optional and defines what to do when the service doesn't exist
$newsletterManager = $containerBuilder->get('newsletter_manager', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
以下是所有可能的行為:
ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE
:在編譯時拋出例外(這是預設行為);ContainerInterface::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE
:在執行時,當嘗試存取遺失的服務時拋出例外;ContainerInterface::NULL_ON_INVALID_REFERENCE
:傳回null
;ContainerInterface::IGNORE_ON_INVALID_REFERENCE
:忽略要求參考的包裝命令(例如,如果服務不存在,則忽略 setter);ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE
:忽略/傳回null
以用於未初始化的服務或無效的參考。
避免您的程式碼變得依賴容器
雖然您可以直接從容器中檢索服務,但最好盡量減少這種情況。例如,在 NewsletterManager
中,您注入了 mailer
服務,而不是從容器中請求它。您可以注入容器並從中檢索 mailer
服務,但這樣它就會與這個特定的容器綁定,使其難以在其他地方重複使用該類別。
您需要在某些時候從容器中取得服務,但這應該盡可能少次地在應用程式的入口點進行。
使用組態檔設定容器
除了使用上面的 PHP 設定服務之外,您也可以使用組態檔。這讓您可以使用 XML 或 YAML 來編寫服務的定義,而不是像上面的範例那樣使用 PHP 來定義服務。在除了最小型的應用程式之外的任何應用程式中,將服務定義組織起來,將它們移到一個或多個組態檔中是合理的。為此,您還需要安裝Config 組件。
載入 XML 組態檔
1 2 3 4 5 6 7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');
載入 YAML 組態檔
1 2 3 4 5 6 7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yaml');
注意
如果您想要載入 YAML 組態檔,那麼您還需要安裝Yaml 組件。
提示
如果您的應用程式使用非常規的檔案副檔名(例如,您的 XML 檔案具有 .config
副檔名),您可以將檔案類型作為 load()
方法的第二個可選參數傳遞:
1 2
// ...
$loader->load('services.config', 'xml');
如果您確實想要使用 PHP 來建立服務,那麼您可以將其移到單獨的組態檔中,並以類似的方式載入它:
1 2 3 4 5 6 7
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
$container = new ContainerBuilder();
$loader = new PhpFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.php');
您現在可以使用組態檔設定 newsletter_manager
和 mailer
服務:
1 2 3 4 5 6 7 8 9 10 11 12
parameters:
# ...
mailer.transport: sendmail
services:
mailer:
class: Mailer
arguments: ['%mailer.transport%']
newsletter_manager:
class: NewsletterManager
calls:
- [setMailer, ['@mailer']]