问题
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