How can I pass an empty value through Zend framework 2 ValidatorChain to my custom validator?
It was possible on ZF1 by allowEmpty(false)
On ZF2 with empty element value :
If allowEmpty = false, NotEmptyValidator is added to the top of ValidatorChain with breakOnFailure = true, #see Zend/InputFilter/Input#injectNotEmptyValidator.
If allowEmpty = true, Element is considered as Valid, #see Zend/InputFilter/BaseInputFilter#isValid
if ($input->allowEmpty()) {
$this->validInputs[$name] = $input;
continue;
}
continue_if_empty solved my problem. Thanks to #dson-horĂ¡cio-junior. This is what I used:
$this->add(array(
'name' => 'field',
'continue_if_empty' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim')
),
'validators' => array(
array(
'name' => 'Application\Form\Validator\Sample'
)
)
));
public function isValid($value, $context = null)
{
if ($value == '' && $context['otherfield'] == '') {
$this->error(self::INVALID_FIELD);
return false;
}
// ...
}
Following works for ZF2 version 2.1.1:
The problem (if I got it correctly) is that in following example, for empty values of 'fieldName', no validation is triggered. This can be quite annoying, though in
$input = new \Zend\InputFilter\Input('fieldName');
$input
->setAllowEmpty(true)
->setRequired(false)
->getValidatorChain()
->attach(new \Zend\Validator\Callback(function ($value) {
echo 'called validator!';
return true; // valid
}));
$inputFilter = new \Zend\InputFilter\InputFilter();
$inputFilter->add($input);
$inputFilter->setData(array('fieldName' => 'value'));
var_dump($inputFilter->isValid()); // true, echoes 'called validator!'
$inputFilter->setData(array('fieldName' => ''));
var_dump($inputFilter->isValid()); // true, no output
$inputFilter->setData(array());
var_dump($inputFilter->isValid()); // true, no output
This is quite annoying when you have particular cases, like checking an URL assigned to a page in your CMS and avoiding collisions (empty URL is still an URL!).
There's a way of handling this for empty strings, which is to basically attach the NotEmpty validator on your own, and avoiding calls to setRequired and setAllowEmpty. This will basically tell Zend\InputFilter\Input#injectNotEmptyValidator not to utomatically attach a NotEmpty validator on its own:
$input = new \Zend\InputFilter\Input('fieldName');
$input
->getValidatorChain()
->attach(new \Zend\Validator\NotEmpty(\Zend\Validator\NotEmpty::NULL))
->attach(new \Zend\Validator\Callback(function ($value) {
echo 'called validator!';
return true; // valid
}));
$inputFilter = new \Zend\InputFilter\InputFilter();
$inputFilter->add($input);
$inputFilter->setData(array('fieldName' => 'value'));
var_dump($inputFilter->isValid()); // true, echoes 'called validator!'
$inputFilter->setData(array('fieldName' => ''));
var_dump($inputFilter->isValid()); // true, echoes 'called validator!'
$inputFilter->setData(array());
var_dump($inputFilter->isValid()); // false (null was passed to the validator)
If you also want to check against null, you will need to extend Zend\InputFilter\Input as following:
class MyInput extends \Zend\InputFilter\Input
{
// disabling auto-injection of the `NotEmpty` validator
protected function injectNotEmptyValidator() {}
}
This triggered validation of my Callback validator when the value was an empty string:
'required' => false,
'allow_empty' => false,
'continue_if_empty' => true,
'validators' => array(
array(
'name' => 'Callback',
'options' => array(
'callback' => function ($value, $context = []) use ($self) {
// ...
}
)
)
)
The allow_empty initially invalidates the empty string and the continue_if_empty allows it to then be evaluated by the validators that follow.
I see often the people making the mistake using allowEmpty in the inputFilter config arrays. The string should be written with underscore separation not with camel case. So allow_empty will work:
'fieldName' => array(
'name' => 'fieldName',
'required' => true,
'allow_empty' => true,
'filters' => array(
//... your filters ...
)
'validators' => array(
//... your validators ...
),
);
meaning a field with key 'fieldName' must be present in the data, but its value is allowed to be empty.
If you like to use a separate form validate class or a array notation for validate, you can do as follows:
$factory = new Zend\InputFilter\Factory();
$inputFilter = new Zend\InputFilter\InputFilter();
$inputFilter->add($factory->createInput(array(
'name' => 'name',
'required' => false,
'allowEmpty' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => '8',
'max' => '100',
),
),
),
)));
You can pass an array with required => false and allowEmpty => true to input filter factory (as I remember you can pass it directly to input filter too - not so sure).
Related
I have a required ISBN field that I would like to change the generic error messages for. I have set up the custom error messages and they work. The filters are part of the getInputFilterSpecification() function of the models fieldSet which validates and shows the custom error messages correctly:
use Zend\Form\Element;
use Zend\Form\Fieldset;
use Zend\Validator;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Stdlib\Hydrator\ClassMethods as ClassMethodsHydrator;
class BookItemFieldset extends Fieldset implements InputFilterProviderInterface
{
public $Types;
public function __construct($itemName, $Types)
{
parent::__construct($itemName, $Types);
$this->Types = $Types;
// .. Other fields
$bookISBN = new Element\Number('bookISBN');
$bookISBN->setLabel('Book ISBN ')
->setAttribute('id', 'bookISBN');
// .. more fields
$this->add($bookISBN);
}
public function getInputFilterSpecification()
{
return array(
'bookISBN' => array(
'required' => true,
'validators' => array(
new Validator\NotEmpty(array(
'setMessage'=> 'ISBN is Required'
)
),
new Validator\Isbn(array(
'setMessage'=> 'ISBN is Invalid'
)),
)
),
//... more input filters
);
}
}
But when the field is left blank. Both "ISBN is Invalid" and "ISBN is required" messages appear.
Is there a way to only show the required error message if the field is left blank?
something like the following:
'bookISBN' => array(
'required' => array(
'required' => true,
'setMessage' => 'ISBN is required' // Only this shows if field is empty
),
'validators' => array(
new Validator\Isbn(array(
'setMessage'=> 'ISBN is Invalid' // only this shows if the input is invalid
)),
)
),
Thanks.
You need to break the chain by setting it to true, which by default is set to false by Zend. Can you please try and see if below code works.
public function getInputFilterSpecification()
{
return array(
'bookISBN' => array(
'required' => true,
'validators' => array(
array(
'name' => 'not_empty',
'break_chain_on_failure' => true,
'options' => array(
'messages' => array(
\Zend\Validator\NotEmpty::IS_EMPTY => 'ISBN is required',
),
),
),
new Validator\Isbn(array(
'setMessage'=> 'ISBN is Invalid'
)),
)
),
//... more input filters
);
}
or may be it is 'NotEmpty'. I can't test it out.
public function getInputFilterSpecification()
{
return array(
'bookISBN' => array(
'required' => true,
'validators' => array(
array(
'name' => 'NotEmpty',
'break_chain_on_failure' => true,
),
new Validator\Isbn(array(
'setMessage'=> 'ISBN is Invalid'
)),
)
),
//... more input filters
);
}
I have followed the zf2 album example. I am now trying to integrate the HTML Purifier.
This is the Module
https://github.com/juriansluiman/Soflomo-Purifier#injecting-the-filtermanager
This is the way I have constructed my inputfilter.
http://framework.zend.com/manual/current/en/user-guide/forms-and-actions.html
namespace Album\Model;
// Add these import statements
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
class Album implements InputFilterAwareInterface
{
public $id;
public $artist;
public $title;
protected $inputFilter; // <-- Add this variable
public function exchangeArray($data)
{
$this->id = (isset($data['id'])) ? $data['id'] : null;
$this->artist = (isset($data['artist'])) ? $data['artist'] : null;
$this->title = (isset($data['title'])) ? $data['title'] : null;
}
// Add content to these methods:
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
$inputFilter->add(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
));
$inputFilter->add(array(
'name' => 'artist',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
));
$inputFilter->add(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
));
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
}
I am getting the following error.
As such, you get a ServiceNotFoundException: Zend\Filter\FilterPluginManager::get was unable to fetch or create an instance for htmlpurifier. This means the filter plugin manager was lazily instantiated, and does not know about the htmlpurifier plugin.
What is the proper way to construct my input filter to take advantage of this module. Many Thanks,
Matt
The problem you're experiencing is caused exactly by what is noted in SoflomoPurifier documentation:
since you're manually instantiating with new InputFilter, that instance is never wired with the configuration provided by additional modules.
To ensure this wiring happens, I would suggest to create a new InputFilter subclass and put your all the InputFilter::add() calls into its init() method:
use Zend\InputFilter\InputFilter:
class AlbumInputFilter extends InputFilter
{
public function init()
{
$this->add(array(
'name' => 'id',
'required' => true,
'filters' => array(
array('name' => 'Int'),
),
));
$this->add(array(
'name' => 'artist',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
));
$this->add(array(
'name' => 'title',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
),
));
}
}
change your Album model constructor to use constructor injection:
use Zend\InputFilter\InputFilterInterface;
class Album
{
// etc etc
public function __construct(InputFilterInterface $inputFilter)
{
$this->inputFilter = $inputFilter;
}
// etc etc
}
and then use a factory to inject it:
// in your Module.php
public function getServiceConfig()
{
return [
'factories' => [
'AlbumFactory' => function($serviceManager) {
$inputFilterManager = $serviceManager->get('InputFilterManager');
return function() use ($inputFilterManager) {
$inputFilter = $inputFilterManager->get(AlbumInputFilter::class);
return new Album($inputFilter)
}
}
]
];
}
invoking InputFilterPluginManager::get() with an input filter FQCN will automatically invoke its constructor, wire it up with other modules, and then invoke its init() method.
so now, rather than using $album = new Album, you should use the AlbumFactory service to create albums with their input filter correctly injected:
$createAlbum = $serviceManager->get('AlbumFactory');
$album = $createAlbum()
obviously you are advised to inject this factory inside any consumer (i.e. controllers) instead of pulling it from the service manager, and possibly refactor the anonymous function into a dedicated factory class.
I have this code in my Form.php
$this->add(array(
'name' => 'unidades_andar',
'type' => 'number',
'attributes' => array(
'class' => 'form-control',
),
));
And this in my view.phtml
<?php echo $this->formElement($form->get('unidades_andar')); ?>
And when I try to submit the form, I have this error:
Array ( [unidades_andar] => Array ( [isEmpty] => Value is required and
can't be empty ) )
I already tried to set "required => false".
And if I change the type to TEXT instead of NUMBER, it works.
But why can't I use the type number? It seem to always be required...
If you look at the source of of zend-framework/zend-form/src/Element/Number.php you can see that this field is being set to required by default.
/**
* Provide default input rules for this element
*
* Attaches a number validator, as well as a greater than and less than validators
*
* #return array
*/
public function getInputSpecification()
{
return array(
'name' => $this->getName(),
'required' => true,
'filters' => array(
array('name' => 'Zend\Filter\StringTrim')
),
'validators' => $this->getValidators(),
);
}
So you need to do something like this
public function getInputFilterSpecification()
{
return [
[
"name"=>"unidades_andar",
'required' => false,
'allow_empty' => true, // this will allow submitting empty values like ''
],
];
}
I use Zend\Validator\Hostname to validate an input string containing a url. The problem is that it only accepts URLs of type mydomain.com and not with a http:// or https:// protocol prefix. What is the best way to achieve the desired behaviour?
$inputFilter->add($factory->createInput(array(
'name' => 'shop_link',
'required' => false,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'min' => 1,
'max' => 100,
),
),
array(
'name' => 'Hostname',
'options' => array(
'allow' => \Zend\Validator\Hostname::ALLOW_DNS, // Allow these hostnames
'useIdnCheck' => true, // Check IDN domains
'useTldCheck' => true, // Check TLD elements
'ipValidator' => null, // IP validator to use
),
),
),
)));
Thanks
I don't have 50 reputation to comment Sam's answer, but Hostname::ALLOW_URI doesn't work. And didn't work year ago - https://stackoverflow.com/a/13902180/822947. And shouldn't work, if you look at the source code. Strange that it has two upvotes.
$validator = new \Zend\Validator\Hostname();
$validator->setAllow($validator::ALLOW_DNS | $validator::ALLOW_URI);
var_dump($validator->isValid('http://domain.com')); // false!
There's two things you should try, first one will probably already be your desired result:
'options' => array(
'allow' => Hostname::ALLOW_URI | Hostname::ALLOW_DNS,
....
)
Allowing the URI, at least going by the RFC-Specification, should include the validation of the Scheme...
In addition to that, there is also a Filter that you may want to know about: Zend\Filter\UriNormalize
I've used Uri validator and disabled allowRelative (set to false) for validating URIs:
$this->add(array(
'name' => 'url',
'validators' => array(
array(
'name' => 'Uri',
'options' => array(
'allowRelative' => false
),
),
),
));
Try this:
public function isValidUrl($url) {
$uri = new \Zend\Validator\Uri();
if (!$uri->isValid($url)) {
return FALSE;
}
$parseUrl = parse_url($url);
if(!isset($parseUrl['host']) || empty($parseUrl['host'])) {
return FALSE;
}
$validator = new \Zend\Validator\Hostname(\Zend\Validator\Hostname::ALLOW_DNS);
if (!$validator->isValid($parseUrl['host'])) {
return FALSE;
}
if (!filter_var($parseUrl['host'], FILTER_VALIDATE_URL) === false) {
return FALSE;
}
return TRUE;
}
I would like to use SeparatorToSeparator() filter in zend framwork 2 to filter my data.How can I pass two arguments(setSearchSeparator and setReplacementSeparator) to the constructor?
$inputFilter->add(array(
'name' => 'supplierName',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
array('name'=>"Word\SeparatorToSeparator"
),
));
Path:zendframework/bin/libary/Zend/Filter/Word/SeparatorToSeparator.php
class SeparatorToSeparator extends AbstractFilter
{
protected $searchSeparator = null;
protected $replacementSeparator = null;
/**
* Constructor
*
* #param string $searchSeparator Separator to search for
* #param string $replacementSeparator Separator to replace with
*/
public function __construct($searchSeparator = ' ', $replacementSeparator = '-')
{
$this->setSearchSeparator($searchSeparator);
$this->setReplacementSeparator($replacementSeparator);
}
Updated
$inputFilter->add(array(
'name' => 'supplierName',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
array('name'=>'Word\SeparatorToSeparator',
'options' => array(
'search_separator' => 'a',
'replacement_separator' => 'b'
)
)
),
));
I got this error message:
Warning: preg_quote() expects parameter 1 to be string, array given in
C:\wamp\www\tebipdevelopment\vendor\zendframework\zendframework\library\Zend\Filter\Word\SeparatorToSeparator.php
on line 92
I've opened this line and I have printed the error message like this.
print_r($this->searchSeparator);
print_r($this->replacementSeparator);
Result
Array ( [search_separator] => a [replacement_separator] => b )
In that case the search_separator is equals to array instead of string
Note you don't need setters, but i've added them in anyway, the filter will try and use setters if they exist (setCamelCase() notation).
class SeparatorToSeparator extends AbstractFilter
{
protected $searchSeparator = null;
protected $replacementSeparator = null;
public function setSearchSeparator($val)
{
$this->searchSeparator = $val;
}
public function setReplacementSeparator($val)
{
$this->replacementSeparator = $val;
}
}
Now you can set the options:
$inputFilter->add(array(
'name' => 'supplierName',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
array(
'name'=>"Word\SeparatorToSeparator"
'options' => array(
'search_separator' => 'bla',
'replacement_separator' => 'bla'
)
)
),
));
I've come up against this same issue, unlike other validators, the word validators don't accept an array of options, as you've found. The workaround I used was to just instantiate the word filter first, passing it the required constructor params, and then add that instance to the filters spec...
$wordFilter = new \Zend\Filter\Word\SeparatorToSeparator('a', 'b');
$inputFilter->add(array(
'name' => 'supplierName',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
$wordFilter,
),
));