跳到內容

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_managermailer 服務:

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']]
本作品,包括程式碼範例,以 創用 CC 姓名標示-相同方式分享 3.0 授權條款授權。
目錄
    版本