Zend ServiceManager使用setter注入

时间:2018-11-14 11:20:03

标签: zend-framework zend-framework3

在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中解决我的问题的更好解决方案?

2 个答案:

答案 0 :(得分:2)

基于这个问题以及您之前关于Forms,Fieldsets和InputFilters的问题,我认为您想实现与以下用例相似的功能。

用例

您有

  • 位置实体
  • 地址实体
  • 位置对地址具有一个一对一(必填,单向)

要求

要管理位置,您需要:

  • LocationForm(-工厂)
  • LocationFormInputFilter(-Factory)
  • LocationFieldset(-Factory)
  • LocationFieldsetInputFilter(-Factory)
  • AddressFieldset(-Factory)
  • AddressFieldsetInputFilter(-Factory)

配置

要在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,例如删除空的(子字段集/集合)输入,这样就不会尝试在其中创建数据数据库)

LocationFieldset中,为位置添加add输入,例如名称和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中,仅添加地址实体的输入。 (与上面相同,没有字段集类型输入)

InputFilters

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

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注入的问题所在。这发生在工厂中。

*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;
}

*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;
}

*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;
}

*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
    );
}

注意

  • 我已添加here
  • ,将InputFilter设置为(不是)必需的。
  • 如果您的InputFilter(例如AddressFieldsetInputFilter)没有子InputFilter,则可以跳过获取该子项并立即返回新的InputFilter。

我想我已经完整地介绍了所有内容。如果对此有任何疑问,请发表评论。

答案 1 :(得分:1)

您需要的是来自Zend Service Manager初始化器

初始化程序可以是在创建服务时调用的类。 在该类中,您需要检查所创建的服务的类型,以及是否合适的类型而不是注入任何所需的服务。

要注册一个初始化器,请在 service_manager 键下的config中添加:

'service_manager' => [
    'initializers' => [
       MyInitializer::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。