跳到内容

注入类型

编辑此页

显式地声明一个类的依赖,并要求将它们注入到该类中,是使类更可重用、可测试和与其他类解耦的好方法。

有几种方法可以注入依赖。每种注入点都有其优缺点需要考虑,以及在使用服务容器时不同的工作方式。

构造器注入

注入依赖最常见的方式是通过类的构造器。为此,您需要在构造器签名中添加一个参数来接受依赖。

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 可以强制依赖在服务的生命周期内保持不变。
  • 这种类型的注入与 Trait 配合良好,因为服务可以被组合,这样,根据您的应用程序需求调整服务变得更加容易。
  • 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 类似,这种类型的注入与 Trait 配合良好,并允许您组合您的服务。

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 许可协议获得许可。
目录
    版本