Zend ServiceManager 使用 setter 注入

Zend ServiceManager using setter injection

在 symfony 中,我可以通过 call 选项 (https://symfony.com/doc/current/service_container/calls.html)

使用 setter 注入服务

symfony 文档中的示例:

class MessageGenerator
{
    private $logger;

    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    // ...
}

service.yml

services:
    App\Service\MessageGenerator:
        # ...
        calls:
            - method: setLogger
              arguments:
                  - '@logger'

我的 zend 项目需要这种行为。我想将 InputFilter 注入我的 FormFieldSet.

我在 zend 文档中没有找到任何相关信息。我可以使用类似的东西或存在更好的解决方案来解决我在 zend 中的问题吗?

根据这个问题和你之前关于表单、字段集和输入过滤器的问题,我认为你想要实现类似于以下用例的东西。

用例

你有一个

  • 位置实体
  • 地址实体
  • Location 具有 OneToOne 到 Address(必需,单向)

要求

要管理位置,您需要:

  • LocationForm(-工厂)
  • LocationFormInputFilter(-工厂)
  • LocationFieldset(-工厂)
  • LocationFieldsetInputFilter(-工厂)
  • 地址字段集(-工厂)
  • AddressFieldsetInputFilter(-工厂)

配置

要在 ZF3 中配置它,您必须添加以下内容

'form_elements' => [
    'factories' => [
        AddressFieldset::class  => AddressFieldsetFactory::class,
        LocationForm::class     => LocationFormFactory::class,
        LocationFieldset::class => LocationFieldsetFactory::class,
    ],
],
'input_filters' => [
    'factories' => [
        AddressFieldsetInputFilter::class  => AddressFieldsetInputFilterFactory::class,
        LocationFormInputFilter::class     => LocationFormInputFilterFactory::class,
        LocationFieldsetInputFilter::class => LocationFieldsetInputFilterFactory::class,
    ],
],

表单和字段集

LocationForm 中,添加您的 LocationFieldset 以及您的表单需要的其他内容,例如 CSRF 和提交按钮。

class LocationForm extends AbstractForm
{
    public function init()
    {
        $this->add([
            'name'    => 'location',
            'type'    => LocationFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);

        //Call parent initializer. Adds CSRF & submit button
        parent::init();
    }
}

(注:我的AbstractForm做的有点多,建议你看看here,比如remove empty (child fieldsets/collections) Inputs so data is not attempted将在数据库中创建)

LocationFieldset 中,为位置添加输入,例如名称,以及 AddressFieldset:

class LocationFieldset extends AbstractFieldset
{
    public function init()
    {
        parent::init();

        $this->add([
            'name'     => 'name',
            'required' => true,
            'type'     => Text::class,
            'options'  => [
                'label' => _('Name'),
            ],
        ]);

        $this->add([
            'type'     => AddressFieldset::class,
            'name'     => 'address',
            'required' => true,
            'options'  => [
                'use_as_base_fieldset' => false,
                'label'                => _('Address'),
            ],
        ]);
    }
}

AddressFieldset 中只需添加地址实体的输入。 (同上,不带Fieldset类型Input)

输入过滤器

要验证表单,您可以使其非常简单:

class LocationFormInputFilter extends AbstractFormInputFilter
{
    /** @var LocationFieldsetInputFilter  */
    protected $locationFieldsetInputFilter;

    public function __construct(LocationFieldsetInputFilter $filter) 
    {
        $this->locationFieldsetInputFilter = $filter;

        parent::__construct();
    }

    public function init()
    {
        $this->add($this->locationFieldsetInputFilter, 'location');

        parent::init();
    }
}

AbstractFormInputFilter 添加 CSRF 验证器)

请注意,我们只是 ->add() LocationFieldsetInputFilter,但我们给它起了一个名字(第二个参数)。此名称稍后会在完整结构中使用,因此保持简单和正确都很重要。最简单的是给它一个名称,该名称与它应该验证的 Fieldset 的对象一对一匹配。

接下来,LocationFieldsetInputFilter:

class LocationFieldsetInputFilter extends AbstractFieldsetInputFilter
{
    /**
     * @var AddressFieldsetInputFilter
     */
    protected $addressFieldsetInputFilter;

    public function __construct(AddressFieldsetInputFilter $addressFieldsetInputFilter) 
    {
        $this->addressFieldsetInputFilter = $addressFieldsetInputFilter;

        parent::__construct();
    }

    public function init()
    {
        parent::init();

        $this->add($this->addressFieldsetInputFilter, 'address'); // Again, name is important

        $this->add(
            [
                'name'       => 'name',
                'required'   => true,
                'filters'    => [
                    ['name' => StringTrim::class],
                    ['name' => StripTags::class],
                    [
                        'name'    => ToNull::class,
                        'options' => [
                            'type' => ToNull::TYPE_STRING,
                        ],
                    ],
                ],
                'validators' => [
                    [
                        'name'    => StringLength::class,
                        'options' => [
                            'min' => 3,
                            'max' => 255,
                        ],
                    ],
                ],
            ]
        );
    }
}

工厂

现在,您必须将它们绑定在一起,我想这就是您关于 Setter 注入的问题的来源。这发生在工厂里。

A *FormFactory 将执行以下操作:

public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
    $inputFilterPluginManager = $container->get('InputFilterManager');
    $inputFilter = $inputFilterPluginManager->get(LocationFormInputFilter::class);

    /** @var LocationForm $form */
    $form = new LocationForm();
    $form->setInputFilter($inputFilter); // The setter injection you're after

    return $form;
}

A *FieldsetFactory 将执行以下操作(对 Location- 和 AddressFieldsets 执行相同操作):

public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
    /** @var LocationFieldset $fieldset */
    // name matters! Match the object to keep it simple. Name is used from Form to match the InputFilter (with same name!)
    $fieldset = new LocationFieldset('location'); 
    // Zend Reflection Hydrator, could easily be something else, such as DoctrineObject hydrator. 
    $fieldset->setHydrator(new Reflection()); 
    $fieldset->setObject(new Location());

    return $fieldset;
}

A *FormInputFilterFactory 会执行以下操作:

public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
    $inputFilterPluginManager = $container->get('InputFilterManager');

    /** @var LocationFieldsetInputFilter $locationFieldsetInputFilter */
    $locationFieldsetInputFilter = $inputFilterPluginManager->get(LocationFieldsetInputFilter::class);

    // Create Form InputFilter
    $locationFormInputFilter = new LocationFormInputFilter(
        $locationFieldsetInputFilter
    );

    return $locationFormInputFilter;
}

A *FieldsetInputFilterFactory 将执行以下操作:

public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
    /** @var AddressFieldsetInputFilter $addressFieldsetInputFilter */
    $addressFieldsetInputFilter = $this->getInputFilterManager()->get(AddressFieldsetInputFilter::class);
    $addressFieldsetInputFilter->setRequired(true);

    return new LocationFieldsetInputFilter(
        $addressFieldsetInputFilter
    );
}

:

  • 将 InputFilter 设置为(不是)必需的是我添加的东西 here
  • 如果您的 InputFilter(例如 AddressFieldsetInputFilter)没有子 InputFilter,您可以跳过获取子 InputFilter 并直接 return 新的 InputFilter。

我想我已经涵盖了所有内容以获得完整的图片。如果您对此有任何疑问,请发表评论。

您需要的是 Zend Service Manager 中的 Initializers

初始化程序可以是 class,只要创建服务就会调用它。 在那个 class 中,您需要检查创建的服务类型,如果它是合适的类型,那么就注入您想要的任何内容。

要在 service_manager 键下的配置中注册一个初始化程序:

'service_manager' => [
    'initializers' => [
       MyInitializer::class
    ],
]

然后创建 class

class MyInitializer implements InitializerInterface
{
    public function __invoke(ContainerInterface $container, $instance)
    {
        // you need to check should you inject or not
        if ($instance instanceof MessageGenerator) { 
            $instance->setLogger($container->get('logger'));
        }
    }
}

您还需要在 zend-servicemanager 中注册 MessageGenerator。这样,当您尝试从 SM 检索 MessageGenerator 时,会在创建后调用 MyInitializer。