Validation error "This value should not be blank" when submitting a form on production website - forms

I'm developing a website using php 7.4, symfony 5.4 and twig. This website is deployed on several servers.
On one of the servers (RedHat), a form cannot be submitted. I get the following error 4 times : "This value should not be blank.".
The messages appear on top of the form and aren't attached to a particular field.
I can't reproduce this error on another server, nor on my development environment...
The problem might comes from a validator but I'm not sure whether it's a symfony or a doctrine error.
The POST data is identical on production server and dev environment :
report_selection[iFrame]: 1
report_selection[dteFrom]: 2023-01-30 07:00
report_selection[dteTo]: 2023-01-31 07:00
report_selection[reportType]: 1
report_selection[size]: 200
report_selection[product]: 1
report_selection[submit]:
I assume that the empty field submit is not a problem since other forms work fine while having the same field empty.
The database structure is the same on all servers.
Here is the form's code :
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$bDisplaySize = $options['bDisplaySize'];
$bDisplayReportType = $options['bDisplayReportType'];
$bDisplayProduct = $options['bDisplayProduct'];
$defaultValue = $options['defaultValue'];
$em = $options['entity_manager'];
list($H, $m) = explode(":", $iShiftStart);
$initialFromDate = (new DateTime())->modify('-'.$H.' hour');
$initialFromDate = $initialFromDate->modify('-1 day');
$initialFromDate->setTime((int)$iShiftStart, (int)$m, 0);
$initialToDate = clone $initialFromDate;
$initialToDate = $initialToDate->modify('+1 day');
$builder->add(
'iFrame',
ChoiceType::class,
array(
'label' => 'master.preselection',
'choices' => [
'master.yesterday' => false,
'master.today' => false,
'master.thisWeek' => false,
'master.lastWeek' => false,
'master.thisMonth' => false,
'master.lastMonth' => false,
'master.memomryDate' => false,
],
'attr' => ['onchange' => 'refreshPreselectedChoices()'],
'choice_attr' => [
'master.yesterday' => [],
'master.today' => ['selected' => 'selected'],
'master.thisWeek' => [],
'master.lastWeek' => [],
'master.thisMonth' => [],
'master.lastMonth' => [],
'master.memomryDate' => ['disabled' => true],
],
)
);
$builder->add(
'dteFrom',
TextType::class,
array(
'label' => 'form.from',
'data' => $initialFromDate->format('Y-m-d H:i'),
'attr' => array(
'style' => 'width:150px;',
'oninput' => 'dteFromToCustom()',
'onchange' => 'dteFromToCustom()',
),
)
);
$builder->add(
'dteTo',
TextType::class,
array(
'label' => 'form.to',
'data' => $initialToDate->format('Y-m-d H:i'),
'attr' => array(
'label' => 'form.to',
'style' => 'width:150px;',
'oninput' => 'dteFromToCustom()',
'onchange' => 'dteFromToCustom()',
),
)
);
if ($bDisplayReportType) {
$builder->add(
'reportType',
ChoiceType::class,
array(
'label' => 'summaryReport.data',
'choices' => array(
'summaryReport.type1' => '1',
'summaryReport.type2' => '2',
),
)
);
}
if ($bDisplaySize) {
$builder->add(
'size',
EntityType::class,
array(
'class' => ProductsSizeSpecs::class,
'choice_label' => 'rSize',
'choice_value' => 'rSize',
'placeholder' => '',
'label' => 'form.size',
'required' => false,
'mapped' => false,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('e')
->groupBy('e.rSize')
->orderBy('e.rSize', 'ASC');
},
)
);
}
if ($bDisplayProduct) {
$builder->add(
'product',
EntityType::class,
array(
'class' => Products::class,
'choice_label' => 'sNumber',
'choice_value' => 'sNumber',
'placeholder' => '',
'label' => 'master.product',
'required' => false,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('e')
->groupBy('e.sNumber')
->orderBy('e.sNumber', 'ASC');
},
)
);
}
$builder->add(
'submit',
SubmitType::class,
array(
'label' => 'form.submit',
'attr' => array('class' => 'btn btn-primary'),
)
);
}
Other forms use the exact same code with more or less options.
I search a way to debug this on the production server (list/dump of 'blank' fields?).
Any hint will be appreciated, thanks !

Indeed I had some #Assert\NotBlank on several columns of an Entity.
Why the error was only on this server :
An instance (db record) of this Entity was NULL on those columns (which is an anormal behavior).
All the instances where retrieved to populate the form's dropdowns (as 'default' data).
It looks like the validator is checking the submitted 'data' AND those 'default' values since they are part of the form.
There were 4 asserted columns, so that's why I had 4 errors messages.
What I've done to find this out :
Added a dump($this->form->getErrors()) instruction on the callback processing the submitted form. It displayed the 4 entity's columns giving me hard time.
Went into db to see the corrupted record, and deleted it.
To prevent this in the future I might change the default values of these columns from NULL to something else, a basic string or a 0 value, and search the process that led to this corrupted record in db.
Thanks for your hints guys !

Related

Doctrine ChoiceType setting a default value on form load

I have created a form on a Symfony CRM using the buildForm() function in a form type file. This form includes a choice drop down consisting of simple "yes" and "no" options which map to 1 and 0 respectively. I need to be able to have "no" as the default as my client more often will select this option over the "yes". After reading the documentation here I figured that the preferred_choices option would suit my needs.
Here is my entry in the buildForm() :
$builder->add('non_rider', ChoiceType::class,
array(
'label' => 'Is Non-Rider',
'required' => true,
'placeholder' => false,
'choices' => array(
'Yes' => 1,
'No' => 0
),
'preferred_choices' => array(0,1),
'label_attr' => array(
'class' => 'control-label'
),
'attr' => array(
'class' => 'form-control required'
)
));
However, this brings out the order as "Yes" then "No" with "Yes" as the default selected option. I was wondering if it reads 0 as null, which means it doesn't register? Is there any way to make "No" the auto-selected option on form load?
You can use the "data" option as mentioned here symfony.com/doc/current/reference/forms/types/choice.html, and show in action here http://stackoverflow.com/a/35772605/2476843
$builder->add('non_rider', ChoiceType::class,
array(
'label' => 'Is Non-Rider',
'required' => true,
'placeholder' => false,
'choices' => array(
'Yes' => 1,
'No' => 0
),
'data' => 0,
'preferred_choices' => array(0,1),
'label_attr' => array(
'class' => 'control-label'
),
'attr' => array(
'class' => 'form-control required'
)
));

Radio button: input was not found in the haystack?

Whenever I submit the form I get this message:
The input was not found in the haystack.
This is for the shipping-method element (radio button). Can't figure out what it means, the POST data for that element is not null.
public function getInputFilter()
{
if (!$this->inputFilter) {
$inputFilter = new InputFilter();
// Some other basic filters
$inputFilter->add(array(
'name' => 'shipping-method',
'required' => true,
'filters' => array(
array('name' => 'StripTags'),
array('name' => 'StringTrim')
),
'validators' => array(
array(
'name' => 'StringLength',
'options' => array(
'encoding' => 'UTF-8',
'max' => 20,
),
),
array(
'name' => 'Db\RecordExists',
'options' => array(
'table' => 'shipping',
'field' => 'shipping_method',
'adapter' => $this->dbAdapter
)
),
),
));
$inputFilter->get('shipping-address-2')->setRequired(false);
$inputFilter->get('shipping-address-3')->setRequired(false);
$this->inputFilter = $inputFilter;
}
return $this->inputFilter;
}
I only keep finding solutions for <select>.
Here's the sample POST data:
object(Zend\Stdlib\Parameters)#143 (1) {
["storage":"ArrayObject":private] => array(9) {
["shipping-name"] => string(4) "TEST"
["shipping-address-1"] => string(4) "test"
["shipping-address-2"] => string(0) ""
["shipping-address-3"] => string(0) ""
["shipping-city"] => string(4) "TEST"
["shipping-state"] => string(4) "TEST"
["shipping-country"] => string(4) "TEST"
["shipping-method"] => string(6) "Ground"
["submit-cart-shipping"] => string(0) ""
}
}
UPDATE:
form.phtml
<div class="form-group">
<?= $this->formRow($form->get('shipping-method')); ?>
<?= $this->formRadio($form->get('shipping-method')
->setValueOptions(array(
'Ground' => 'Ground',
'Expedited' => 'Expedited'))
->setDisableInArrayValidator(true)); ?>
</div>
ShippingForm.php
$this->add(array(
'name' => 'shipping-method',
'type' => 'Zend\Form\Element\Radio',
'options' => array(
'label' => 'Shipping Method',
'label_attributes' => array(
'class' => 'lbl-shipping-method'
),
)
));
The problem lies with when you use the setValueOptions() and the setDisableInArrayValidator(). You should do this earlier within your code as it is never set before validating your form and so the inputfilter still contain the defaults as the InArray validator. As after validation, which checks the inputfilter, you set different options for the shipping_methods.
You should move the setValueOptions() and the setDisableInArrayValidator() before the $form->isValid(). Either by setting the right options within the form itsself or doing this in the controller. Best way is to keep all of the options in one place and doing it inside the form class.
$this->add([
'name' => 'shipping-method',
'type' => 'Zend\Form\Element\Radio',
'options' => [
'value_options' => [
'Ground' => 'Ground',
'Expedited' => 'Expedited'
],
'disable_inarray_validator' => true,
'label' => 'Shipping Method',
'label_attributes' => [
'class' => 'lbl-shipping-method',
],
],
]);
Another small detail you might want to change is setting the value options. They are now hardcoded but your inputfilter is checking against database records whether they exist or not. Populate the value options with the database records. If the code still contains old methods but the database has a few new ones, they are not in sync.
class ShippingForm extends Form
{
private $dbAdapter;
public function __construct(AdapterInterface $dbAdapter, $name = 'shipping-form', $options = [])
{
parent::__construct($name, $options)
// inject the databaseAdapter into your form
$this->dbAdapter = $dbAdapter;
}
public function init()
{
// adding form elements to the form
// we use the init method to add form elements as from this point
// we also have access to custom form elements which the constructor doesn't
$this->add([
'name' => 'shipping-method',
'type' => 'Zend\Form\Element\Radio',
'options' => [
'value_options' => $this->getDbValueOptions(),
'disable_inarray_validator' => true,
'label' => 'Shipping Method',
'label_attributes' => [
'class' => 'lbl-shipping-method',
],
],
]);
}
private function getDbValueOptions()
{
$statement = $this->dbAdapter->query('SELECT shipping_method FROM shipping');
$rows = $statement->execute();
$valueOptions = [];
foreach ($rows as $row) {
$valueOptions[$row['shipping_method']] = $row['shipping_method'];
}
return $valueOptions;
}
}
Just had this happen yesterday.
The select and multi select ZF2+ elements have a built in in_array validator.
Remember filters occur before validators.
You may be doing too much here -- it is very rare to need to filter or add validators ot select and multi select form elements in ZF2 forms. The built in element validator is robust, ZF does a lot of work for us.
Try removing both filter and validator for the element, such as:
$inputFilter->add(array(
'name' => 'shipping-method',
'required' => true,
));
There is another edge case that I have seen: changing the select element's valueOptions somewhere in the controller (or view) resulting in different valueOptions used in view vs form validation (in our case it was replacing the element with a new one before validation).
I think your problem lies in the fact you are adding your value options after the InArray validator has been set, hence the validator has no haystack.
Try this
$this->add(array(
'name' => 'shipping-method',
'type' => 'Zend\Form\Element\Radio',
'options' => array(
'label' => 'Shipping Method',
'label_attributes' => array(
'class' => 'lbl-shipping-method'
),
'value_options' => array(
'Ground' => 'Ground',
'Expedited' => 'Expedited'
),
'disable_inarray_validator' => TRUE,
)
));
and remove setValueOptions and setDisableInArrayValidator from your view.
Hope this works.

Zend framework 2 form element normalization

I am migrating an application from Zend 1 to Zend 2 and starting to desperate with one issue. The application works with different locales and therefore, I need to store the data in a normalized way in the database. In Zend 1 I used this code:
public function normalizeNumber( $value )
{
// get the locale to change the date format
$this->_locale = Zend_Registry::get('Zend_Locale' );
return Zend_Locale_Format::getNumber($value, array('precision' => 2, 'locale' => $this->_locale));
}
Unfortunately Zend 2 does not has this Zend_Locale_Format::getNumber any more and I was not able to figure out what function did replace it. I have tried with NumberFormat, but I get only localized data not normalized. I need this function to normalize data I receive from a form via POST. Can someone give some advice?
thanks
Just to complete my question. The Form element definition I am using is the following:
namespace Profile\Form;
use Zend\Form\Form;
use Zend\InputFilter\InputFilterProviderInterface;
class Profile Extends Form implements InputFilterProviderInterface
{
protected $model;
public function __construct( $model, $name = 'assignmentprofile')
{
parent::__construct( $name );
$this->setAttribute( 'method', 'post');
$this->model = $model;
...
$this->add( array(
'name' =>'CommutingRate',
'type' =>'Zend\Form\Element\Text',
'options' => array( // list of options to add to the element
'label' => 'Commuting rate to be charged:',
'pattern' => '/[0-9.,]/',
),
'attributes' => array( // Attributes to be passed to the HTML lement
'type' =>'text',
'required' => 'required',
'placeholder' => '',
),
));
}
public function getInputFilterSpecification()
{
return array(
...
'CommutingRate' => array(
'required' => true,
'filters' => array(
array( 'name' => 'StripTags', ),
array( 'name' => 'StringTrim'),
array( 'name' => 'NumberFormat', 'options' => array('locale' => 'en_US', 'style' => 'NumberFormatter::DECIMAL', 'type' => 'NumberFormatter::TYPE_DOUBLE',
))
),
'validators' => array(
array( 'name' => 'Float',
'options' => array( 'messages' => array('notFloat' => 'A valid numeric entry is required')),
),
),),
...
);
}
}
As mentioned before, I am able to localized the data and validate it in the localized manner, but i am failing to convert it back to a normalized manner...

Symfony set value checked on a form type choice

I create a form with FormBuilder with Symfony like :
$builder
->add('timeBarOpen', 'time', array('label' => 'Ouverture Bar', 'attr' => array('class' => 'form-control')))
->add('timeBarClose', 'time', array('label' => 'Fermeture Bar', 'attr' => array('class' => 'form-control')))
->add('timeStartHappyHour', 'time', array('label' => 'Début Happy Hour *', 'attr' => array('class' => 'form-control')))
->add('timeEndHappyHour', 'time', array('label' => 'Fin Happy Hour *', 'attr' => array('class' => 'form-control')))
->add('day', 'choice', [
'choices' => $days,
'multiple' => true,
'expanded' => true,
'label' => 'Jour(s) *',
])
;
$days is an array :
$days = array(
'Monday' => 'Lundi',
'Tuesday' => 'Mardi',
'Wednesday' => 'Mercredi',
'Thursday' => 'Jeudi',
'Friday' => 'Vendredi',
'Saturday' => 'Samedi',
'Sunday' => 'Dimanche',
);
So, this field type "choice" generates multiple checkboxes and I need them all to be checked by defaut when the form is created.
How can I do that?
You can use the data parameters to specify some default choices, in your case specify an array, and use the keys of your available choices
$builder
->add('day', 'choice', [
'choices' => $days,
'multiple' => true,
'expanded' => true,
'label' => 'Jour(s) *',
'data' => array_keys($days)
])
;
I had a similar problem with a ChoiceType drop-down list, where I wanted to be able to set the selected value, but I couldn't figure out how to do that. I figured it out from #ThomasPiard 's answer. Thank you!
In my example, I set the 'choices', and the 'data' is set to the value of the array (not the key). This is important - since I couldn't figure out why it didn't work at first.
Here's my sample:
->add('pet_type', ChoiceType::class, array( // Select Pet Type.
'choices' => array(
'Substitution' => 'sub',
'Equivalency' => 'equiv',
),
'label' => 'Select Petition Type:',
'attr' => array(
'onchange' => 'changedPetType()',
),
'placeholder' => 'Choose an option',
'data' => 'equiv',
))
Hopefully it will help someone with the same problem.

Symfony2: How to translate custom error messages in form types?

I need to translate the error messages from my form type. Here is my form Type code:
class ReferFriendType extends AbstractType {
public function buildForm(FormBuilder $builder, array $options)
{
$defaultSubject = "This is a default referral subject.";
$defaultMessage = "This is a default referral message.";
$builder->add('email1', 'email',array(
'required' => true,
'label' => 'Email 1* :',
'attr' => array('class' => 'large_text'),
));
$builder->add('email2', 'email',array(
'label' => 'Email 2 :',
'required' => false,
'attr' => array('class' => 'large_text'),
));
$builder->add('email3', 'email',array(
'label' => 'Email 3 :',
'required' => false,
'attr' => array('class' => 'large_text'),
));
$builder->add('email4', 'email',array(
'label' => 'Email 4 :',
'required' => false,
'attr' => array('class' => 'large_text'),
));
$builder->add('email5', 'email',array(
'label' => 'Email 5 :',
'required' => false,
'attr' => array('class' => 'large_text'),
));
$builder->add('subject', 'text', array(
'data' => $defaultSubject,
'required' => true,
'label' => 'Subject* :',
'attr' => array('class' => 'large_text'),
));
$builder->add('message', 'textarea', array(
'data' => $defaultMessage,
'required' => true,
'label' => 'Message* :',
'attr' => array('rows' => '5', 'cols' => '40'),
));
}
public function getDefaultOptions(array $options)
{
$collectionConstraint = new Collection( array(
'fields' => array(
'email1' => array(
new Email(),
new NotBlank(array(
'message' => 'You must enter atleast one email address for a valid submission',
)),
),
'subject' => new NotBlank(),
'message' => new NotBlank(),
),
'allowExtraFields' => true,
'allowMissingFields' => true,
));
return array(
'validation_constraint' => $collectionConstraint,
'csrf_protection' => false,
);
}
public function getName()
{
return 'referFriend';
}
}
I want to translate 'You must enter atleast one email address for a valid submission' in getDefaultOptions() method into french. I have added the translation in the messages.fr.yml. But it is not getting translated. Any ideas how this can be done?
Validation translations go to the validators.LANG.yml files — not messages.LANG.yml ones.
The replacements are not set in the validation.yml file but by the Validator.
validators.en.yml
noFirstnameMinLimit: Please provide at least {{ limit }} characters
validation.yml
Acm\AddressBundle\Entity\Address:
properties:
firstname:
- Length:
min: 3
minMessage: "noFirstnameMinLimit"
This works for me with Symfony 2.4
There is an example in the docs.
It`s easy, see http://symfony.com/doc/current/book/translation.html#translating-constraint-messages
And set default_locale in /app/config/config.yml or play with $this->get('request')->setLocale('ru');