跳到內容

注入的類型

編輯此頁面

明確指出類別的依賴性,並要求將它們注入到類別中,是使類別更具可重用性、可測試性並與其他類別解耦的好方法。

有多種方式可以注入依賴性。每種注入點都有其優缺點需要考慮,以及在使用服務容器時與它們協同工作的不同方式。

建構子注入

注入依賴性的最常見方式是透過類別的建構子。為此,您需要在建構子簽名中新增一個引數來接受依賴性

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

// ...
class NewsletterManager
{
    public function __construct(
        private MailerInterface $mailer,
    ) {
    }

    // ...
}

您可以在服務容器配置中指定您想要注入到此的服務

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

    App\Mail\NewsletterManager:
        arguments: ['@mailer']

提示

類型提示注入的物件表示您可以確保已注入合適的依賴性。透過類型提示,如果注入了不合適的依賴性,您會立即獲得明確的錯誤訊息。透過使用介面而不是類別進行類型提示,您可以使依賴性的選擇更具彈性。並且假設您僅使用介面中定義的方法,您仍然可以安全地使用該物件,同時獲得彈性。

使用建構子注入有幾個優點

  • 如果依賴性是必要條件,並且類別在沒有它的情況下無法運作,那麼透過建構子注入可確保在使用類別時它存在,因為在沒有它的情況下無法建構類別。
  • 建構子僅在建立物件時呼叫一次,因此您可以確保依賴性在物件的生命週期內不會改變。

這些優點確實意味著建構子注入不適用於處理可選的依賴性。它也更難與類別階層結構結合使用:如果一個類別使用建構子注入,那麼擴展它並覆寫建構子會變得有問題。

不可變 Setter 注入

另一種可能的注入是使用一種方法,該方法透過複製原始服務來傳回單獨的實例,這種方法允許您使服務成為不可變的

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
// src/Mail/NewsletterManager.php
namespace App\Mail;

// ...
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Contracts\Service\Attribute\Required;

class NewsletterManager
{
    private MailerInterface $mailer;

    /**
     * @return static
     */
    #[Required]
    public function withMailer(MailerInterface $mailer): self
    {
        $new = clone $this;
        $new->mailer = $mailer;

        return $new;
    }

    // ...
}

為了使用這種注入類型,請不要忘記配置它

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

     app.newsletter_manager:
         class: App\Mail\NewsletterManager
         calls:
             - withMailer: !returns_clone ['@mailer']

注意

如果您決定使用自動連線,則此注入類型要求您新增 @return static docblock 或 static 傳回類型,以便容器能夠註冊該方法。

如果您需要根據您的需求配置您的服務,這種方法非常有用,因此,以下是不可變 Setter 的優點

  • 不可變 setter 適用於可選的依賴性,這樣,如果您不需要依賴性,則無需呼叫 setter。
  • 與建構子注入類似,使用不可變 setter 強制依賴性在服務的生命週期內保持不變。
  • 這種注入類型適用於特徵 (traits),因為服務可以組合,這樣,使服務適應您的應用程式需求變得更容易。
  • setter 可以多次呼叫,這樣,將依賴性新增到集合中變得更容易,並允許您新增可變數量的依賴性。

缺點是

  • 由於 setter 呼叫是可選的,因此在呼叫服務的方法時,依賴性可能為 null。您必須在使用依賴性之前檢查它是否可用。
  • 除非服務被宣告為延遲載入,否則它與在所謂的循環迴路中相互參照的服務不相容。

Setter 注入

另一個可能的注入點是透過新增一個 setter 方法來接受依賴性

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

use Symfony\Contracts\Service\Attribute\Required;

// ...
class NewsletterManager
{
    private MailerInterface $mailer;

    #[Required]
    public function setMailer(MailerInterface $mailer): void
    {
        $this->mailer = $mailer;
    }

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

    app.newsletter_manager:
        class: App\Mail\NewsletterManager
        calls:
            - setMailer: ['@mailer']

這次的優點是

  • Setter 注入適用於可選的依賴性。如果您不需要依賴性,則不要呼叫 setter。
  • 您可以多次呼叫 setter。如果該方法將依賴性新增到集合中,則此方法特別有用。然後您可以擁有可變數量的依賴性。
  • 與不可變 setter 類似,這種注入類型適用於特徵 (traits),並允許您組合您的服務。

Setter 注入的缺點是

  • setter 可以多次呼叫,並且在初始化後很久也可以呼叫,因此您無法確定依賴性在物件的生命週期內不會被替換(除非透過顯式編寫 setter 方法來檢查它是否已被呼叫)。
  • 您無法確定 setter 是否會被呼叫,因此您需要新增檢查以確保注入任何必要的依賴性。

屬性注入

另一種可能性是直接設定類別的公有欄位

1
2
3
4
5
6
7
// ...
class NewsletterManager
{
    public MailerInterface $mailer;

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

    app.newsletter_manager:
        class: App\Mail\NewsletterManager
        properties:
            mailer: '@mailer'

使用屬性注入主要只有缺點,它與 setter 注入類似,但存在這個額外的重大問題

  • 您根本無法控制何時設定依賴性,它可以在物件生命週期的任何時間點被更改。

但是,了解可以使用服務容器完成此操作很有用,特別是當您使用不受您控制的程式碼時,例如在第三方程式庫中,該程式庫對其依賴性使用公有屬性。

這項工作(包括程式碼範例)根據 Creative Commons BY-SA 3.0 授權條款獲得許可。
目錄
    版本