I'm not sure what's going on. I'm using Zend Form 2 with a multiselect field. When I submit the code, the values exist in post. When I run the values through zend form 2, I get no validation errors but the multiselect field is suddenly empty.
class Form extends \Zend\Form\Form
{
// input filter to set up filters and validators
protected $myInputFilter;
public function __construct()
{
// create the zend form
parent::__construct();
// make it a bootstrap form
$this->setAttribute('class', 'form-horizontal');
$this->setAttribute('role', 'form');
// set the default objects we'll use to build the form validator
$this->myInputFilter = new \Zend\InputFilter\InputFilter();
}
}
class AddPublicationForm extends Form
{
public function __construct()
{
// create the zend form
parent::__construct();
$this->setAttribute('class', 'form-horizontal');
$this->setAttribute('id', 'add-publication-form');
$this->add([
'name' => 'author[]',
'attributes' => [
'class' => 'form-control',
'data-placeholder' => 'Author',
'multiple' => 'multiple',
'placeholder' => 'Author',
],
'required' => false,
'type' => \Zend\Form\Element\Select::class,
'options' => [
'value_options' => [
'check1' => 'check1',
'check2' => 'check2',
],
],
]);
$this->myInputFilter->add([
'filters' => [],
'name' => 'author[]',
'required' => false,
'validators' => [],
]);
// attach validators and filters
$this->setInputFilter($this->myInputFilter);
// prepare the form
$this->prepare();
}
}
These are the zend form objects that I am using. I am using Slim Framework 2 as my backend. Here is the controller object:
public function addAction()
{
$request = $this->app->request;
$form = new Form\AddPublicationForm();
if ($request->isPost()) {
$params = $request->params();
// DUMP 1: exit('<pre>'.print_r($params, true).'</pre>');
$form->setData($params);
if ($form->isValid()) {
$data = $form->getData();
// DUMP 2: exit('<pre>'.print_r($data, true).'</pre>');
}
}
}
DUMP 1:
Array
(
[author] => Array
(
[0] => check1
[1] => check2
)
}
DUMP 2:
Array
(
[author[]] =>
)
I realize that I could very easily just bypass the validation here because I'm not using any validators on that field. I'm more concerned with the underlying cause though.
Why is the validated author data empty?
When you specify multiple in attributes, Zend\Form and Zend\InputFilter add []after the name. You should not do it yourself otherwise, in the html code, the element appears under the name author[][] and the setData method don't match.
To see it, replace required by true and look at the html code of the form.
$this->add([
'name' => 'author',
'attributes' => [
'class' => 'form-control',
'data-placeholder' => 'Author',
'multiple' => 'multiple',
'placeholder' => 'Author',
],
'required' => false,
'type' => \Zend\Form\Element\Select::class,
'options' => [
'value_options' => [
'check1' => 'check1',
'check2' => 'check2',
],
],
]);
$this->myInputFilter->add([
'filters' => [],
'name' => 'author',
'required' => false,
'validators' => [],
]);
Related
I have a form. The form has a Collection whose target element is a fieldset with a checkbox and a couple of text fields. The fieldset attached as the target element to Collection looks like this (simplified to avoid too much code):
class AFieldset extends Fieldset implements InputFilterProviderInterface
{
public function __construct(HydratorInterface $hydrator)
{
parent::__construct();
$this->setHydrator($hydrator)
->setObject(new SomeObject());
$this->add([
'type' => Hidden::class,
'name' => 'id',
]);
$this->add([
'type' => Checkbox::class,
'name' => 'selectedInForm',
]);
$this->add([
'type' => Text::class,
'name' => 'textField1',
]);
$this->add([
'type' => Text::class,
'name' => 'textField2',
]);
}
public function getInputFilterSpecification()
{
return [
'selectedInForm' => [
'required' => false,
'continue_if_empty' => true,
'validators' => [
['name' => Callback::class // + options for the validator],
],
],
'id' => [
'requred' => false,
'continue_if_empty' => true,
],
'textField1' => [
'required' => false,
'continue_if_empty' => true,
'validators' => [
['name' => SomeValidator::class],
],
],
'textField2' => [
'required' => true,
'validators' => [
['name' => SomeValidator::class],
],
],
],
}
}
I'd like to validate textField1 and textField2 based on if selectedInForm checkbox is checked in the form.
How could I do this?
I though of using a Callback validator for selectedInForm checkbox like this:
'callback' => function($value) {
if ($value) {
$this->get('textField1')->isValid();
// or $this->get('textField1')->getValue() and do some validation with it
}
}
but the problem with it is that, for some reason, the posted value of textField1 value isn't attached to the input yet. Same is true for textField2.
Two option is available. One is where you started, with callback validators.
The other one is to write a custom validator, and to make it reusable I recommend this solution.
<?php
use Zend\Validator\NotEmpty;
class IfSelectedInFormThanNotEmpty extends NotEmpty
{
public function isValid($value, array $context = null): bool
{
if (! empty($context['selectedInForm']) && $context['selectedInForm']) {
return parent::isValid($value);
}
return true;
}
}
And then you can use it as every other validator:
'textField2' => [
'required' => true,
'validators' => [
['name' => IfSelectedInFormThanNotEmpty::class],
],
],
This may not be your exact case, but I hope it helped to get the idea.
You may define options to make it more reusable with a configurable conditional field in public function __construct($options = null).
In ZF2, I get the form from the controller factory like this:
class SomeControllerFactory implements FactoryInterface
{
public function CreateService(SeviceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
// other things from service manager
$registrationForm = $realServiceLocator->get('FormElementManager')
->get('Path\To\My\Form\RegistrationForm');
}
return new SomeController(
// controller dependencies, including $registrationForm
);
}
In the RegistrationForm, I have MultiCheckBox:
$this->add([
'type' => 'Zend\Form\Element\MultiCheckBox',
'name' => 'partyRoleIds',
'options' => [
'label' => 'Отношение',
'value_options' => [
[
'value' => '1',
'label' => 'client',
],
[
'value' => '2',
'label' => 'prospect'],
[
'value' => '6',
'label' => 'contractor',
],
],
],
]);
I want to populate value_options from a db query that returns an array like [1 => 'client', 2 => 'prospect'...]. Populating is not an problem, but I don't know how to pass this array as a dependency into the RegistrationForm because in the call $registrationForm = $realServiceLocator->get('FormElementManager')->get('Path\To\My\Form\RegistrationForm');, I don't have any place to add the dependency.
How could I do this?
PS: rewritten the question, please forgive my initial brevity.
In form classes you add the method :
public function setValueOptions($element, array $values_options)
{
$e = $this->get($element);
$e->setValueOptions($values_options);
return $this;
}
In your controller, if your form is $registrationForm you write :
$registrationForm->setValueOptions('partyRoleIds', $valueOptions);
where $valueOptions is an array like your sample.
I have a simple form currently consisting of a single fieldset. Now I want the fields to be filtered and validated. So I implemented the method getInputFilterSpecification() in my Fieldset class:
...
class FooFieldset extends \Zend\Form\Fieldset
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->setHydrator(new ClassMethods(false));
$this->setObject(new Buz());
$this->setLabel('Baz');
$this->add(array(
'type' => 'text',
'name' => 'bar',
'options' => array(
'label' => _('bar')
)
));
}
public function getInputFilterSpecification()
{
return [
'bar' => [
'required' => true,
'filters' => [
0 => [
'name' => 'Zend\Filter\StringTrim',
'options' => []
]
],
'validators' => [],
'description' => _('bar lorem ipsum'),
'allow_empty' => false,
'continue_if_empty' => false
]
];
}
}
and added the Fieldset to the Form:
...
class BuzForm extends \Zend\Form\Form
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->setAttribute('role', 'form');
$this->add(array(
'name' => 'baz-fieldset',
'type' => 'Buz\Form\BazFieldset'
));
$this->add(array(
'type' => 'submit',
'name' => 'submit',
'attributes' => array(
'value' => 'preview'
)
));
}
}
The problem is, that the InputFilter specifications are completely ignored. I've set a breakpoint into FooFieldset#getInputFilterSpecification() and to be sure even checked it witha die() -- the method is not called.
What is wrong here and how to get it working correctly?
You will need to implement Zend\InputFilter\InputFilterProviderInterface interface in order for the getInputFilterSpecification() method to be called.
I have a moneyFieldset with 2 fields, amount and currency.
class MoneyFieldset ...
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->setHydrator(...);
$this->add(array(
'name' => 'currency',
'type' => 'select',
'options' => array(
'value_options' => \Core\Service\Money::getAvailableCurrencies(true),
),
'attributes' => array(
'value' => \Core\Service\Money::DEFAULT_CURRENCY,
),
));
$this->add(array(
'name' => 'amount',
'type' => 'text',
));
}
}
public function getInputFilterSpecification()
{
$default = [
'amount' => [
'required' => false,
'allow_empty' => true,
'filters' => [
['name' => AmountFilter::class]
],
'validators' => [
]
],
'currency' => [
'required' => false,
'allow_empty' => true,
'filters' => [
['name' => StringToUpper::class]
],
'validators' => [
]
]
];
return \Zend\Stdlib\ArrayUtils::merge($default, $this->filterSpec, true);
}
I'm using moneyFieldset in my other fieldsets like this:
// Price Field
$this->add(array(
'name' => 'price',
'type' => 'form.fieldset.moneyFieldset',
'attributes' => array(
'required' => true,
'invalidText' => 'Please type an amount'
),
'options' => array(
...
),
));
When I set filter like this:
function getInputFilterSpecification()
{
'price' => [
'required' => true,
'allow_empty' => false,
],
}
It's not working because price has 2 fields, so how can I say price[amount] and price[curreny] is required?
You can provide the input specifications for the nested fieldset (within the context of the form) using the following array structure.
public function getInputFilterSpecification()
{
return [
// ...
'price' => [
'type' => 'Zend\InputFilter\InputFilter',
'amount' => [
'required' => true,
],
'currency' => [
'required' => true,
]
],
//...
];
}
If you are dynamically modifying the values of the input filter it might be worth considering creating the validator using a service factory class and then attaching it to a form using the object API rather than arrays.
As I said in #AlexP's comment, a field, or a group of field declared as Required like this :
function getInputFilterSpecification()
{
'price' => [
'required' => true,
'allow_empty' => false,
],
}
Not means that it will be print an html like this :
<input type="text" required="required"/>
It just check when you'll do $form->isValid() if your fields are empty and required or other checks.
To achieve that, you just have to set in attributes that you want to require those fields. As you already did. Attributes can add, same as class attribute, html code to an input.
P.S : AlexP's answer is the best answer I just give more info about it.
I need use form element with out isEmpty validation. This is my code.
$this->add(array(
'name' => 'test',
'type' => 'Zend\Form\Element\Number',
'attributes' => array(
'class' => 'form-control',
)
));
But following validation message is given.
[test] => Array
(
[isEmpty] => Value is required and can't be empty
)
How can i remove it?
Look here:
https://github.com/zendframework/zf2/blob/master/library/Zend/Form/Element/Number.php#L95
You can extend this class and overload getInputSpecification function and return array without 'required' => true
Like this:
namespace Your\Form\Elements;
use Zend\Form\Element\Number;
class NumberWithoutRequired extends Number{
public function getInputSpecification()
{
return array(
'name' => $this->getName(),
'required' => false,
'filters' => array(
array('name' => 'Zend\Filter\StringTrim')
),
'validators' => $this->getValidators(),
);
}
}
And then use this class for input in Your form instead of original Zend\Form\Element\Number class
If you have a specific form class, add a getInputFilterSpecification method with your validation rules:
class MyForm extends \Zend\Form\Form
{
public function init() // or __construct() if not using element manager
{
$this->add(array(
'name' => 'test',
'type' => 'Zend\Form\Element\Number',
'attributes' => array(
'class' => 'form-control',
)
));
}
public function getInputFilterSpecification()
{
return [
'test' => [
'required' => false,
]
];
}
}
You could do that by creating new ValidatorChain, and then loop through the validators attached to your element and dettach the Zend\Validator\NotEmpty validator. Just like this :
$newValidatorChain = new \Zend\Validator\ValidatorChain;
foreach ($form->getInputFilter()->get('test')->getValidatorChain()->getValidators()
as $validator)
{
//Attach all validators except the \Zend\Validator\NotEmpty one
if (!($validator['instance'] instanceof \Zend\Validator\NotEmpty)) {
$newValidatorChain->addValidator($validator['instance'],
$validator['breakChainOnFailure']);
}
}
$form->getInputFilter()->get('test')->setValidatorChain($newValidatorChain);