Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

Input Filter Factory uses a different FilterPluginManager instance #79

Closed
ffraenz opened this issue Nov 22, 2015 · 2 comments
Closed

Input Filter Factory uses a different FilterPluginManager instance #79

ffraenz opened this issue Nov 22, 2015 · 2 comments

Comments

@ffraenz
Copy link

ffraenz commented Nov 22, 2015

I created a custom filter which needs dependency injection by a factory. I added the filter to the config array like so:

return  [
    'filters' => [
        'factories' => [
            'ToFile' => 'Module\Factory\Filter\ToFile',
        ],
    ],
];

I am able to get a filter instance from the FilterPluginManager inside a controller:

$filter = $this->getServiceLocator()->get('FilterManager')->get('ToFile');
var_dump($filter);

Inside the init() method of a class extending Zend\Form\Form I configured an input filter using the custom filter. The form also uses custom form elements from the FormElementManager.

$this->getInputFilter()->add([
    'name' => 'file',
    'required' => false,
    'filters' => [
        [ 'name' => 'ToFile' ],
    ],
]);

Custom form elements are working fine but the custom filter always raises a ServiceNotFoundException:

Zend\Filter\FilterPluginManager::get was unable to fetch or create an instance for ToFile

I found out that the Input Filter Factory is using a different FilterPluginManager instance which has never seen the config array. The custom filter with its factory is not included in the plugin manager.

This is unexpected behaviour? Or am I wrong?

@weierophinney
Copy link
Member

This is expected behavior.

To get the input filter as configured at the application level, you need to do the following:

  • Your input filter needs to be registered as a service under the input_filters key (this follows standard ServiceManager configuration paradigms); this ensures that when you retrieve it, its factory is injected with the configured filter and validator plugin managers.
  • Your form needs to be registered as a service under the 'form_elements` key (this follows standard ServiceManager configuration paradigms); this ensures that when you fetch the form, it is configured using hydrators configured in your hydrator plugin manager and input filters configured in your input filter plugin manager (which was the above point).
  • Finally, you will then need to either pull your form (and thus its related input filter) from the form_elements plugin manager, and/or use a factory to do so in order to inject your form into the appropriate controller.

Alternately, you can set all this up semi-manually.

In your controller, you will do something like the following:

$services = $this->getServiceLocator();
$inputFilters = $services->get('InputFilterManager');
$inputFilter = new YourCustomInputFilter();
$inputFilterFactory = $inputFilter->getFactory()->setInputFilterManager($inputFilters);
$inputFilter->init();

A simpler approach would be to register your input filter with the InputFilterManager. Assuming you have no constructor dependencies, it could be registered as an invokable:

return [
    'input_filters' => [
        'invokables' => [
            'YourCustomInputFilter' => 'FullyQualfied\Namespace\For\YourCustomInputFilter',
        ],
    ],
];

From there, you could do the following in your controller:

$services = $this->getServiceLocator();
$inputFilters = $services->get('InputFilterManager');
$inputFilter = $inputFilters->get('YourCustomInputFilter');

Even better would be to inject it directly into your controller via the constructor, and to create a factory for your controller. The tutorial demonstrates such factories.

What the above approaches do is ensure that any input filters or inputs you've defined in the InputFilterManager are injected in your custom input filter's internal factory — and this simultaneously pulls the configured FilterManager and ValidatorManager and sets them in the default filter and validator chains, ensuring that you have access to those filters and validators you defined in modules and/or the application level.

The reason it works this way is because ZF2 does not define any globally static plugin managers; you must inject them where you want them. This prevents a whole category of problems we encountered in ZF1, and makes your code ultimately more testable (particularly if you inject the input filter into the controller instead of pulling it from the service manager within your controller).

@ffraenz
Copy link
Author

ffraenz commented Nov 23, 2015

Thank you very much for this detailed answer!

I want to define the input filters next to their related form elements inside a class extending Zend\Form\Form, not as different services. I retrieve the form instance through the FormElementManager which does inject the form factory with its dependencies. That's why the custom form elements are available in my form.

When the object bound to the form does not implement InputFilterAwareInterface, the form creates its InputFilter instance itself when getInputFilter gets called (as described here):

$this->filter = new InputFilter();

In this case, the Zend\InputFilter\Factory dependency of this newly created input filter instance does not get satisfied which results in creating a different factory instance with separate ServiceManagers.

The form has a pointer to a Zend\InputFilter\Factory instance with properly injected ServiceManagers. It uses it here.

Adding following line right after the statement shown above makes custom filters and validators available inside the form as expected:

$this->filter->setFactory($this->getFormFactory()->getInputFilterFactory());

I wanted to continue the discussion here before opening a pull request in the zend-form repository. Let me know your thoughts about this!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants