I had a form that had two fields. An InputFilter with validators was applied to it. It was working fine. Then I moved the fields to a fieldset and added the fieldset to the form. Now the assignment validators to the fields is not present. The validator objects isValid
method is not triggered at all. So how to apply the InputFilter validators to fields in a fieldset? Here you are the classes:
Text class Validator
namespace Application\Validator;
use Zend\Validator\StringLength;
use Zend\Validator\ValidatorInterface;
class Text implements ValidatorInterface
{
protected $stringLength;
protected $messages = [];
public function __construct()
{
$this->stringLengthValidator = new StringLength();
}
public function isValid($value, $context = null)
{
if (empty($context['url'])) {
if (empty($value)) return false;
$this->stringLengthValidator->setMin(3);
$this->stringLengthValidator->setMax(5000);
if ($this->stringLengthValidator->isValid($value)) {
return true;
}
$this->messages = $this->stringLengthValidator->getMessages();
return false;
}
if (!empty($value)) return false;
return true;
}
public function getMessages()
{
return $this->messages;
}
}
Test class InputFilter
namespace Application\Filter;
use Application\Fieldset\Test as Fieldset;
use Application\Validator\Text;
use Application\Validator\Url;
use Zend\InputFilter\InputFilter;
class Test extends InputFilter
{
public function init()
{
$this->add([
'name' => Fieldset::TEXT,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Text::class],
],
]);
$this->add([
'name' => Fieldset::URL,
'required' => false,
'allow_empty' => true,
'continue_if_empty' => true,
'validators' => [
['name' => Url::class],
],
]);
}
}
Test class Fieldset
namespace Application\Fieldset;
use Zend\Form\Fieldset;
class Test extends Fieldset
{
const TEXT = 'text';
const URL = 'url';
public function init()
{
$this->add([
'name' => self::TEXT,
'type' => 'textarea',
'attributes' => [
'id' => 'text',
'class' => 'form-control',
'placeholder' => 'Type text here',
'rows' => '6',
],
'options' => [
'label' => self::TEXT,
],
]);
$this->add([
'name' => self::URL,
'type' => 'text',
'attributes' => [
'id' => 'url',
'class' => 'form-control',
'placeholder' => 'Type url here',
],
'options' => [
'label' => self::URL,
],
]);
}
}
Test class Form
namespace Application\Form;
use Application\Fieldset\Test as TestFieldset;
use Zend\Form\Form;
class Test extends Form
{
public function init()
{
$this->add([
'name' => 'test',
'type' => TestFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
$this->add([
'name' => 'submit',
'attributes' => [
'type' => 'submit',
'value' => 'Send',
],
]);
}
}
TestController class
namespace Application\Controller;
use Application\Form\Test as Form;
use Zend\Debug\Debug;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class TestController extends AbstractActionController
{
private $form;
public function __construct(Form $form)
{
$this->form = $form;
}
public function indexAction()
{
if ($this->getRequest()->isPost()) {
$this->form->setData($this->getRequest()->getPost());
Debug::dump($this->getRequest()->getPost());
if ($this->form->isValid()) {
Debug::dump($this->form->getData());
die();
}
}
return new ViewModel(['form' => $this->form]);
}
}
TestControllerFactory class
namespace Application\Factory;
use Application\Controller\TestController;
use Application\Form\Test;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class TestControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$form = $container->get('FormElementManager')->get(Test::class);
return new TestController($form);
}
}
Test class
namespace Application\Factory;
use Application\Filter\Test as Filter;
use Application\Entity\Form as Entity;
use Application\Form\Test as Form;
use Interop\Container\ContainerInterface;
use Zend\Hydrator\ClassMethods;
use Zend\ServiceManager\Factory\FactoryInterface;
class Test implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return (new Form())
->setHydrator($container
->get('HydratorManager')
->get(ClassMethods::class))
->setObject(new Entity())
->setInputFilter($container->get('InputFilterManager')->get(Filter::class));
}
}
Test Fieldset
namespace Application\Factory;
use Application\Entity\Fieldset as Entity;
use Application\Fieldset\Test as Fieldset;
use Interop\Container\ContainerInterface;
use Zend\Hydrator\ClassMethods;
use Zend\ServiceManager\Factory\FactoryInterface;
class TestFieldset implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
return (new Fieldset())
->setHydrator($container->get('HydratorManager')->get(ClassMethods::class))
->setObject(new Entity());
}
}
UPDATE
I updated the fieldset class accordingly to @Nukeface advise by adding setInputFilter()
. But it did not worked. It even had not executed InpuFilter class init
method. Perhaps I did in wrong:
<?php
namespace Application\Fieldset;
use Application\Filter\Test as Filter;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterAwareTrait;
class Test extends Fieldset
{
use InputFilterAwareTrait;
const TEXT = 'text';
const URL = 'url';
public function init()
{
$this->add([
'name' => self::TEXT,
'type' => 'textarea',
'attributes' => [
'id' => 'text',
'class' => 'form-control',
'placeholder' => 'Type text here',
'rows' => '6',
],
'options' => [
'label' => self::TEXT,
],
]);
$this->add([
'name' => self::URL,
'type' => 'text',
'attributes' => [
'id' => 'url',
'class' => 'form-control',
'placeholder' => 'Type url here',
],
'options' => [
'label' => self::URL,
],
]);
$this->setInputFilter(new Filter());
}
}
Tried an answer before and ran out of chars (30k limit), so created a repo instead. The repo contains abstraction of the answer below, which is a working example.
Your question shows you having the right idea, just not yet the implementation. It also contains a few mistakes, such as setting a FQCN for a Fieldset name. Hopefully the below can have you up and running.
As a use case, we'll have a basic Address form. Relationships for Country, Timezones and other things I'll leave out of the scope. For more in depth and nesting of Fieldsets (also with Collections) I'll refer you to my repo.
General setup
First create the basic setup. Create the Entity and configuration.
Basic Entity
To handle this in a generic and re-usable way, we're going to need:
Configuration
To tie these together in Zend Framework, these need to be registered in the config. With clear naming, you can already add these. If you're using something like PhpStorm as your IDE, you might want to leave this till last, as the
use
statements can be generated for you.As this is an explanation, I'm showing you now. Add this to your module's config:
Fieldset
First we create the Fieldset (and Factory) class. This is because this contains the actual object we're going to handle.
AddressFieldset
AddressFieldsetFactory
InputFilter
Above we created the Fieldset. That allows for the generation of the Fieldset for in a Form. At the same time, Zend Framework also has defaults already set per type of input (e.g.
'type' => Text::class
). However, if we want to validate it to our own, more strict, standards, we need to override the defaults. For this we need an InputFilter class.AddressFieldsetInputFilter
AddressFieldsetInputFilterFactory
Form & Validation
So. Above we created the Fieldset, it's InputFilter and 2 required Factory classes. This already allows us to do a great deal, such as:
Form
Form Factory
Making it all come together
I'll show just the
AddressController#addAction
AddressController
AddressControllerFactory
Display in addressFormPartial
To use this partial, say in a
add.phtml
view, use:This bit of code will work with the demonstrated
addAction
in the Controller code above.Hope you found this helpful ;-) If you have any questions left, don't hesitate to ask.