Zf2 Callback Validator not passing all form values - php

Currently setting up a form using ZF2. I'd like to be able to setup validation for the form based on comparing the values of 2 different elements on the form. Where in the past I've used the Callback validator to pass in all of the forms values, it seems in my current scenario I can only get the value of the element being validated.
Here is how I setup my form
public function __construct(Di $di)
{
$this->setDi($di);
parent::__construct();
$inputFilter = $this->getInputFilter();
$this->addElement(
DateSelect::class,
[
'label' => 'Purchase Date'
], [
'name' => 'purchase_date',
'required' => 'true'
]);
$this->addElement(
Select::class,
[
'label' => 'What card type did you use to make the purchase',
'value_options' => [
'' => '',
'credit' => 'Credit',
'debit' => 'Debit'
]
],[
'required' => true,
'name' => 'card_type',
'helpText' => 'card type'
]);
$this->addElement(Submit::class, [],['value' => 'Claim']);
$inputFilter->add([
'name' => 'card_type',
'validators' => array(
array(
'name' => Callback::class,
'options' => array(
'messages' => array(
Callback::INVALID_VALUE => 'Item must have been purchased in the last 6 years in order to claim',
),
'callback' => function($value, $context = []) {
var_dump(func_get_args());
//I need to be able to get both card_type and purchase_date inputs here
die();
},
)
)
)
]);
}
The problem is that currently var_dump(func_get_args()) is currently only returning:
array(2) { [0]=> string(6) "credit" [1]=> array(1) { ["card_type"]=> string(6) "credit" } }
In the past I would have expected this to also pass in the purchase_date value as a part of the second argument of the callback.
Has anyone experienced this problem before? I'm using the callback function as expected in other areas of my application but can't seem to get it working here.
Thanks in advance
For clarification(although I don't believe it will have any effect on validation), the function $this->addElement is my own function, held on a trait and is simply:
public function addElement($class, $options, $attributes)
{
$this->add(
$this->getDi()->newInstance($class, ['options' => $options])->setAttributes($attributes)
);
}

Turns out that the callback will only get passed other values which the input filter is aware of.
The solution to this is:
$filter->add(['name' => 'card_type', 'required' => true]);

Related

Zend Form 2 multiselect field is empty after validation

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' => [],
]);

zf2/zf3 how to validate dependent inputs in collection's fieldset?

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).

ZF2 - Input type "number" always required?

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 ''
],
];
}

How to add a custom hydrator strategy for doctrine Date hydrator

I have a form which contains 3 field of type text and with a Date validator.
This code is in a fieldset using doctrine hydrator (related to a doctrine entity)
$this->add(
array(
'name' => 'endDate',
'type' => 'Zend\Form\Element\Text',
'options' => array(
'label' => 'end_date_label',
'label_attributes' => array(
'class' => 'control-label col-xs-3'
),
),
'attributes' => array(
'class' => 'form-control col-xs-3 datepicker-end-date',
)
)
);
'endDate' => array(
'required' => true,
'allow_empty' => false,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
),
'validators' => array(
array(
'name' => 'Date',
'options' => array(
'format' => 'd/m/Y',
),
),
),
),
I working with french date format. When i valid the form if i change the format to m/d/Y it's working but when my form is not valid, my date picker get the wrong date (month and days are inversed).
What i want is to valid a french date format, and save into Database a date in m/d/Y format.
With this format i get the error :
DateTime::__construct(): Failed to parse time string (29/04/2015) at position 0 (2): Unexpected character
I saw many post on Stack talking about custom strategy for doctrine hydration but i didn't understand them. What i'm supposed to do step by step ?
I tried to add a strategy for my field endDate but it's never called...This code is in the fieldset class just before my fields declaration :
$this->setHydrator(new DoctrineHydrator($this->getObjectManager(), 'TodoList\Entity\TodoQuestion'))
->setObject(new TodoQuestion());
$this->getHydrator()->addStrategy('endDate', new \Application\Strategy\DateTimeStrategy());
And my Datetime strategy implements strategy interface.
<?php
namespace Application\Strategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class DateTimeStrategy implements StrategyInterface
{
public function hydrate($value)
{
if (is_string($value)) {
$value = new DateTime($value);
}
return $value->format('d/m/Y');
}
public function extract($value)
{
return;
}
}
If someone can explain with details what i'm doing wrong and help me to understand this whole thing..
You should just return a DateTime object from the Strategy.
namespace Application\Strategy;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class DateTimeStrategy implements StrategyInterface
{
public function hydrate($value)
{
if (is_string($value)) {
$value = \DateTime::createFormFormat('d/m/Y', $value);
}
return $value;
}
public function extract($value)
{
return $value;
}
}
Above isn't going to work since strategies are called after the hydrator's type conversion.
You're best of with using a Callback filter.
'endDate' => array(
'required' => true,
'allow_empty' => false,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim'),
array(
'name' => 'Callback',
'options' => array(
'callback' => function($value) {
if (is_string($value)) {
$value = \DateTime::createFromFormat('d/m/Y', $value);
}
return $value;
},
),
),
'validators' => array(
array(
'name' => 'Date',
'options' => array(
'format' => 'd/m/Y',
),
),
),
),
The doctrine hydrator you are using seems outdated btw. Current version doesn't require to specify the entity as a second parameter.
Stumbled onto this question by accident. Though it's reasonably old, I was recently working with Dates in ZF2 forms. I've got my formatting done as below, without using a callback.
Maybe it'll help someone in the future ;)
Below was done using ZF2 2.5.3
$this->add([
'name' => 'startDate',
'required' => true,
'filters' => [
[
'name' => DateTimeFormatter::class,
'options' => [
'format' => 'Y-m-d', // or d/m/Y
],
],
],
'validators' => [
[
'name' => Date::class,
'options' => [
'format' => 'Y-m-d', // or d/m/Y
],
],
],
]);

Nested Parameters/Values in POST Requests

I have been thinking about a good way to handle nested/complex values in POST requests to a apigility resource.
For example, an order might contain a collection of order items in a single POST requested that is used to create an order. Both, order and order-item do exist as a resource. However, I would very much like to have only one request that would create order and order item entities. Handling that in the resource is not a problem, but I wonder how you would configure that resource (let´s call it order-place) using the apigiliy UI - or, if at all impossible, using the configuration. Applying validators and filters is one of the key features of apigility, and i´d like to keep using that, even for complex request data.
And before you ask, using an underscore to separate the values scopes, for example order_comment and order_item_comment should not be an option.
Any ideas?:)
Addition: A sample json request payload could look like this:
{
"created_at": "2000-01-01",
"amount" : "5000.00",
"address" : {
"name": "some name",
"street": "some street"
...
},
"items" : [
{"productId":99,"qty":1}
...
]
}
Starting from Wilt's answer, I've found that the following code works as well:
# file path: /module/MyApi/config/module.config.php
// some other stuff
'MyApi\\V1\\Rest\\MyRestService\\Validator' => array(
'address' => array(
0 => array(
'name' => 'name',
'required' => true,
'filters' => array(),
'validators' => array(),
),
1 => array(
'name' => 'street',
'required' => true,
'filters' => array(),
'validators' => array(),
),
'type' => 'Zend\InputFilter\InputFilter'
),
'amount' => array(
'name' => 'amount',
'required' => true,
'filters' => array(),
'validators' => array()
)
The only problem I get is when address is passed as a field (string or numeric) rather then an array or object. In this case Apigility throws an exception:
Zend\InputFilter\Exception\InvalidArgumentException: Zend\InputFilter\BaseInputFilter::setData expects an array or Traversable argument; received string in /var/www/api/vendor/zendframework/zendframework/library/Zend/InputFilter/BaseInputFilter.php on line 175
Adding address as a further simple (required) field avoids the exception, but then Apigility doesn't see any difference whether we pass address as an array of name and street or a dummy string.
If you are using the ContentValidation module then you can configure an input filter for the nested resources by assigning it to a variable. Then you have to add a type key (essential otherwise reusing the filter won't work). Now you are able to use this variable in your input_filter_specs and you can reuse the whole filter inside another filter. So something like this in your config.php:
<?php
namespace Application;
// Your address config as if it was used independently
$addressInputFilter => array(
'name' => array(
'name' => 'name',
'required' => true,
'filters' => array(
//...
)
'validators' => array(
//...
)
),
'street' => array(
'name' => 'street',
'required' => true,
'filters' => array(
//...
)
'validators' => array(
//...
)
),
// 'type' key necessary for reusing this input filter in other configs
'type' => 'Zend\InputFilter\InputFilter'
),
'input_filter_specs' => array(
// The key for your address if you also want to be able to use it independently
'Application\InputFilter\Address'=> $addressInputFilter,
// The key and config for your other resource containing a nested address
'Application\InputFilter\ItemContainingAddress'=> array(
'address' => $addressInputFilter,
'amount' => array(
'name' => 'amount',
'required' => true,
'filters' => array(
//...
),
'validators' => array(
//...
)
)
//... your other fields
)
)

Categories