Zend ServiceManager using setter injection

风格不统一 提交于 2019-12-11 17:17:23

问题


in symfony i can use the setter injection for services via call option (https://symfony.com/doc/current/service_container/calls.html)

The example from the symfony documentation:

class MessageGenerator
{
    private $logger;

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

    // ...
}

service.yml

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

I need this behaviour for my zend project. i want to inject a InputFilter into my FormFieldSet.

I didn't find anything about this in the zend documentation. Can i use something like this or exist a better solution for my problem in zend?


回答1:


Based on this question and your previous question about Forms, Fieldsets and InputFilters, I'm thinking you want to achieve something similar to the following use case.

Use case

You have a

  • Location Entity
  • Address Entity
  • Location has a OneToOne to an Address (required, uni-directional)

Requirements

To manage the Location, you'll need:

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

Configuration

To configure this in ZF3, you'll have to do add the following

'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,
    ],
],

Forms & Fieldsets

In the LocationForm, add your LocationFieldset and what else your Form needs, such as CSRF and submit button.

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

(Note: my AbstractForm does a bit more, I would suggest you have a look here, such as remove empty (child fieldsets/collections) Inputs so data is not attempted to be created in the DB)

In the LocationFieldset, give add Inputs for the Location, such as a name, and the 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'),
            ],
        ]);
    }
}

In the AddressFieldset just add Inputs for the Address Entity. (Same as above, without the Fieldset type Input)

InputFilters

To validate the Form, you can keep it very simple:

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

(The AbstractFormInputFilter adds CSRF validator)

Notice that we simply ->add() the LocationFieldsetInputFilter, but we give it a name (2nd parameter). This name is used later in the complete structure, so it's important to both keep it simple and keep it correct. Simplest is to give it a name that one on one matches the object of the Fieldset it's supposed to validate.

Next, the 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,
                        ],
                    ],
                ],
            ]
        );
    }
}

Factories

Now, you must bind them together, which is where your question about Setter injection comes from I think. This happens in the Factory.

A *FormFactory would do the following:

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 would do the following (do the same for Location- and 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 would do the following:

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 would do the following:

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

Note:

  • Setting an InputFilter as (not) required is something I've added here
  • If your InputFilter (such as AddressFieldsetInputFilter) does not have a child InputFilter, you can can skip getting the child and straight away return the new InputFilter.

I think I covered it all for a complete picture. If you have any questions about this, please comment.




回答2:


What you need are Initializers from Zend Service Manager.

The initializer can be a class that is called whenever a service has been created. In that class, you need to check the type of service that is created, and if it's appropriate type than inject whatever you want.

To register one Initializer add in config under service_manager key:

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

and then just create that 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'));
        }
    }
}

You need to have registred MessageGenerator in zend-servicemanager also. In this way, when you try to retrive MessageGenerator from SM, after creation MyInitializer is called.



来源:https://stackoverflow.com/questions/53299020/zend-servicemanager-using-setter-injection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!