ZF2 injecting InputFilter into Fieldset not working automatically

不羁的心 提交于 2019-12-25 07:58:49

问题


I'm building a small application with ZF2 and Doctrine2. Setting it up in such a way as to have a lot of reusable code and technique. However, getting stumped by the fact that my InputFilter is not automatically injected into the Fieldset that it should get associated to.

I've confirmed that the Form that uses the Fieldset works (without the InputFilter). The InputFilter is also visible as present during debugging.

The question then, what am I doing wrong and how to fix having a separate InputFilter, coupled to a Fieldset in ZF2?


Sidenotes:

1 - I am aware that by using the InputFilterInterface I could have the InputFilter inside of the Fieldset class with the getInputFilterSpecification() function. However, as I'm trying to keep it DRY and reusable, it wouldn't do to have to copy it if I were to create an API that needs to use the Entity and InputFilter, but can only have the latter coupled with a Fieldset.

2 - A lot of Abstract classes are used, where used I'll indicate in the snippets what they have that's relevant

3 - The problem line is in CustomerFieldsetFactory.php

=========================================================================

Entity: Customer.php

/**
 * Class Customer
 * @package Customer\Entity
 *
 * @ORM\Entity
 * @ORM\Table(name="customers")
 */
class Customer extends AbstractEntity //Contains $id
{
    /**
     * @var string
     * @ORM\Column(name="name", type="string", length=255, nullable=false)
     */
    protected $name;
}

Form: CustomerForm.php

class CustomerForm extends AbstractForm
{
    public function __construct($name = null, array $options)
    {
        parent::__construct($name, $options); // Adds CSRF
    }

    public function init()
    {
        $this->add([
            'name' => 'customer',
            'type' => CustomerFieldset::class,
            'options' => [
                'use_as_base_fieldset' => true,
            ],
        ]);

        //Call parent initializer. Check in parent what it does.
        parent::init(); //Adds submit button if not in form
    }
}

Fieldset: CustomerFieldset.php

class CustomerFieldset extends AbstractFieldset //Contains EntityManager property and constructor requirement (as we're managing Doctrine Entities here)
{
    public function init()
    {
        $this->add([ //For now id field is here, until InputFilter injection works
            'name' => 'id',
            'type' => Hidden::class,
            'attributes' => [
                'id' => 'entityId',
            ],
        ]);

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

InputFilter: CustomerInputFilter.php

class CustomerInputFilter extends AbstractInputFilter
{
    public function init()
    {
        parent::init();

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

Above the classes. Below the Factories

FormFactory: CustomerFormFactory.php

class CustomerFormFactory implements FactoryInterface, MutableCreationOptionsInterface
{
    /**
     * @var array
     */
    protected $options;

    /**
     * @param array $options
     */
    public function setCreationOptions(array $options)
    {
        //Arguments checking removed
        $this->options = $options;
    }

    /**
     * @param ServiceLocatorInterface|ControllerManager $serviceLocator
     * @return CustomerForm
     */
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        $form = new CustomerForm($this->options['name'], $this->options['options']);

        $form->setTranslator($serviceManager->get('translator'));

        return $form;
    }
}

FieldsetFactory: CustomerFieldsetFactory.php

class CustomerFieldsetFactory implements FactoryInterface, MutableCreationOptionsInterface
{
    /**
     * @var string
     */
    protected $name;

    public function setCreationOptions(array $options)
    {
        //Argument checking removed

        $this->name = $options['name'];
    }

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $serviceManager = $serviceLocator->getServiceLocator();

        $fieldset = new CustomerFieldset($serviceManager->get('Doctrine\ORM\EntityManager'), $this->name);

        $fieldset->setHydrator(new DoctrineObject($serviceManager->get('doctrine.entitymanager.orm_default'), false));
        $fieldset->setObject(new Customer());
        $fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class));

        //RIGHT HERE! THE LINE ABOVE IS THE ONE THAT DOES NOT WORK!!!

        return $fieldset;
    }
}

InputFilterFactory: CustomerInputFilterFactory.php

class CustomerInputFilterFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $repository = $serviceLocator->getServiceLocator()
            ->get('Doctrine\ORM\EntityManager')
                ->getRepository(Customer::class);

        return new CustomerInputFilter($repository);
    }
}

Config: module.config.php

'controllers' => [
    'factories' => [
        CustomerController::class => CustomerControllerFactory::class,
    ],
],
'form_elements' => [
    'factories' => [
        CustomerForm::class => CustomerFormFactory::class,
        CustomerFieldset::class => CustomerFieldsetFactory::class,
    ],
],
'input_filters' => [
    'factories' => [
        CustomerInputFilter::class => CustomerInputFilterFactory::class,
    ],
],
'service_manager' => [
    'invokables' => [
        CustomerControllerService::class => CustomerControllerService::class,
    ],
],

I am hoping one of you can help me out here.


EDIT: Update with actual error

The following line in the CustomerFieldset.php (above) triggers the error.

$fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class));

The error:

Fatal error: Call to undefined method Customer\Fieldset\CustomerFieldset::setInputFilter() in D:\htdocs\server-manager\module\Customer\src\Customer\Factory\CustomerFieldsetFactory.php on line 57

As seen in the above snippet, the InputFilter (and it's Factory) are known the the InputFilterManager.

The error states it does not know the getInputFilter() function on the Fieldset. Which is correct in a way, it doesn't exist. The question then is, how to have the function exist so that injecting the InputFilter will work, or how to bind this InputFilter to the Fieldset?


EDIT 2: Update based on Wilt's answer Added use InputFilterAwareTrait to Abstract class AbstractInputFilter to create following (from answer):

use Zend\InputFilter\InputFilterAwareTrait;

abstract class AbstractFieldset extends Fieldset
{
    use InputFilterAwareTrait;
    // ... Other code
}

Turns out that I had another mistake in the (original) code above as well: In file module.config.php the input_filters should've been input_filter_specs. This was (after using the Trait) a Invalid Factory registered error (titled ServiceNotCreatedException).

The following might be of use to someone, the Factory to create a Fieldset with Hydrator, Object and Inputfilter has the following createService() function:

public function createService(ServiceLocatorInterface $serviceLocator)
{
    /** @var ServiceLocator $serviceManager */
    $serviceManager = $serviceLocator->getServiceLocator();
    /** @var CustomerRepository $customerRepository */
    $customerRepository = $serviceManager->get('Doctrine\ORM\EntityManager')->getRepository(Customer::class);

    $fieldset = new CustomerFieldset($serviceManager->get('Doctrine\ORM\EntityManager'), $this->name);
    $fieldset->setHydrator(new DoctrineObject($serviceManager->get('doctrine.entitymanager.orm_default'), false));
    $fieldset->setObject(new Customer());
    $fieldset->setInputFilter($serviceManager->get('InputFilterManager')->get(CustomerInputFilter::class, $customerRepository));

    return $fieldset;
}

回答1:


There is lots of information added to your question. I'd suggest you try to narrow down your question in the future. Read more on the guidelines for a good question here: How to create a Minimal, Complete, and Verifiable example.

Zend framework provides a InputFilterAwareTrait with both the setInputFilter and getInputFilter methods. You can easily implement/use this trait inside your CustomerFieldset class:

use Zend\InputFilter\InputFilterAwareTrait;

class CustomerFieldset extends AbstractFieldset
{
    use InputFilterAwareTrait;

    //...remaining code

}

In case you want the inputfilter in all classes that extend your abstract AbstractFieldset class you could also decide to add the trait there:

use Zend\InputFilter\InputFilterAwareTrait;

class AbstractFieldset
{
    use InputFilterAwareTrait;

    //...remaining code

}



回答2:


See the following question: How to validate nested fieldsets. Fieldsets don't contain InputFilters but you should extend your base InputFilter of your form.

Create an InputFilter for each Fieldset and add them, with the same name as your fieldset, to your InputFilter of your form. As seen within the answer of the other question I linked.

If you don't want to do this you might consider working with InputSpecification.



来源:https://stackoverflow.com/questions/40699072/zf2-injecting-inputfilter-into-fieldset-not-working-automatically

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