注入的類型
明確指出類別的依賴性,並要求將它們注入到類別中,是使類別更具可重用性、可測試性並與其他類別解耦的好方法。
有多種方式可以注入依賴性。每種注入點都有其優缺點需要考慮,以及在使用服務容器時與它們協同工作的不同方式。
建構子注入
注入依賴性的最常見方式是透過類別的建構子。為此,您需要在建構子簽名中新增一個引數來接受依賴性
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 注入類似,但存在這個額外的重大問題
- 您根本無法控制何時設定依賴性,它可以在物件生命週期的任何時間點被更改。
但是,了解可以使用服務容器完成此操作很有用,特別是當您使用不受您控制的程式碼時,例如在第三方程式庫中,該程式庫對其依賴性使用公有屬性。