Validation of AJAX submitted forms in Symfony 5 - php

I'm building an Address form in Symfony 5.1.
It is created in an AddressType Class that has some required fields.
$builder
->add('name', TextType::class, [
'required' => false,
'label' => 'address.name',
'help' => 'address.name_help',
'attr' => [
'placeholder' => 'address.name_ph',
]
])
->add('company', TextType::class, [
'required' => false,
'label' => 'address.company',
'attr' => [
'placeholder' => 'address.company_ph',
]
])
->add('first_line', TextType::class, [
'label' => 'address.first_line',
'attr' => [
'placeholder' => 'address.first_line_ph',
]
])
->add('second_line', TextType::class, [
'required' => false,
'label' => 'address.second_line',
'attr' => [
'placeholder' => 'address.second_line_ph',
]
])
->add('add_info', TextType::class, [
'required' => false,
'label' => 'address.add_info',
'help' => 'address.add_info_help',
'attr' => [
'placeholder' => 'address.add_info_ph',
]
])
->add('postcode', TextType::class, [
'label' => 'address.postcode',
'attr' => [
'placeholder' => 'address.postcode_ph',
]
])
->add('city', TextType::class, [
'label' => 'address.city',
'attr' => [
'placeholder' => 'address.city_ph',
]
])
->add('state', TextType::class, [
'required' => false,
'label' => 'address.state',
'attr' => [
'placeholder' => 'address.state_ph',
]
])
->add('country', CountryType::class, [
'label' => 'address.country',
'preferred_choices' => ['FR'],
'attr' => [
'data-toggle' => 'select',
'placeholder' => 'address.country_ph',
]
])
->add('save',
SubmitType::class,
[
'label' => $options['submit_btn_label'],
]
);
If I submit this form with the submit button, everything works as expected, my form is being processed for validation and if It detects some errors, they are shown on each field.
Here is the function that handle the form :
public function new(Request $request)
{
$user = $this->getUser();
$address = new Address();
$address->setCreatedBy($user);
$form = $this->createForm(AddressType::class, $address);
//handle form
$form->handleRequest($request);
if ($form->isSubmitted()){
//if submit, add hidden fields
$address = $form->getData();
//if valid, process
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($address);
$em->flush();
$this->addFlash(
'success',
'Your address was created.'
);
return $this->redirectToRoute('address_index');
}
}
return $this->render('address/new.html.twig', [
'form' => $form->createView(),
'mode' => 'new',
]);
}
Now, If I submit this form through an AJAX request :
$(document).on('click', '.create-address', function() {
console.log('submitting new address form...');
var $form = $(this).closest('form')
var data = $form.serializeArray();
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data
});
});
In this case, my form is processed for validation and It passes ($form->isValid() returns true) even if I don't provide some of the required fields.
This causes the process of persisting the object to occur and so I get a PDOException.
My question is:
Why is my form not handled the same way (especially at the validation step) according how I post the data ?
And how different are those two methods from the point of view of the function that handle the request ?

The required option only adds the required attribute to the markup. This forces the browser to provide a value, but doesn't add any validation server side. You can trigger validation before your ajax call and submit only if the form status is valid.
But it is recommended to add server side validation explicitly by using the constraints option in the form or annotating the target entity. Refer to the documentation for more details.

Related

How to show field value in sonata edit form?

There is edit form. One field should not be editable. I have tried to set options disabled=true, attr => ['readonly' => true], they make it uneditable, but when submitting form, it gets submitted, sets null to that field and then I get error when getting that field value because it cannot be null.
So I want to make that field not even exist as field but show its value in edit form. Is there a way to do it? Or also if you know how to get rid of error when submitting form with disabled field, that would work too.
public function configureFormFields(FormMapper $form)
{
if ($this->subject->getId() === null) {
$form
->add('name', 'text', ['required' => true])
->add('codeMod', 'text', ['required' => true])
->add('position', 'text', ['required' => false])
->add('projectMod', EntityType::class, ['class' => ProjectEntity::class])
->add('active', 'checkbox', ['required' => false])
->add('first', 'checkbox', ['required' => false])
->add('last', 'checkbox', ['required' => false])
->add('denialReasons', 'text', ['required' => false])
;
} else {
$form
->add('name', 'text', ['required' => true])
->add('position', 'text', ['required' => false])
// ->add('project', TextType::class, ['label' => 'form.label_project_mod', 'attr' => [/*'readonly' => true,*/ 'disabled' => true]])
->add('project', EntityType::class,
['label' => 'form.label_project_mod', 'class' => ProjectEntity::class, 'attr' => ['readonly' => true, 'disabled' => true],
// 'template' => 'ClaimClaimBundle:ClaimStatusAdmin:show_project.html.twig'
]
)
// ->add('projectMod', TextType::class, ['label' => 'form.label_project_mod', 'attr' => [/*'readonly' => true,*/ 'disabled' => true]])
->add('active', 'checkbox', ['required' => false])
->add('first', 'checkbox', ['required' => false])
->add('last', 'checkbox', ['required' => false])
->add('denialReasons', 'text', ['required' => false])
;
}
}
Currently I get error:
Type error: Return value of Qms\ClaimComponent\Status\ManagedModel\StatusManaged::getProject() must implement interface Qms\CoreComponent\Domain\Project\ManagedModel\ProjectManagedInterface, null returned
That is because field value is set to null if I have disabled field.
One way could be rewrite edit.html.twig, now sonatas default template is used. But I did not find quick way, if I override, the styling is off. For one field looks bit too much.
If you don't mind that field's value when submitting you can unmap it by setting
'mapped' => false
in its attributes.
Example:
->add('name', 'text', ['required' => true, 'mapped' => false])

Only Validate if other filed is not empty on symfony form type

I am new to symfony formtypes.
I have a situation that in a form i need to include the change password feature
My formtype is as below
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Image;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
class ProfileFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$imageConstraints = [
new Image([
'maxSize' => '2M'
])
];
$builder
->add('firstName')
->add('lastName')
->add('imageFile', FileType::class, [
'mapped' => false,
'label' => false,
'required' => false,
'error_bubbling' => true,
'constraints' => $imageConstraints
])
->add('imageFileName', HiddenType::class, [
'mapped' => false,
])
->add('oldPassword', PasswordType::class, array('label'=>'Current password', 'mapped' => false,
'required' => false,'error_bubbling' => true,'constraints' => new UserPassword([
'message' => "Please enter user's current password",
])))
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => [
'constraints' => [
// new NotBlank([
// 'message' => 'Please enter a password',
// ]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
'label' => false,
'attr' => [
'class' => 'form-control',
'placeholder' => 'New password',
],
],
'second_options' => [
'label' => false,
'required' => false,
'attr' => [
'class' => 'form-control',
'placeholder' => 'Repeat password',
]
],
'invalid_message' => 'The password fields must match.',
// Instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'required' => false,
'error_bubbling' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => true, 'allow_extra_fields' => true,
'data_class' => User::class,
]);
}
}
I have succesfully implemented the feature.But my problem is i need to enter the oldPassword filed everytime i submit the form else it gives a validation error as need to users current password.
I want to change it as only if the new password is entered then only i need to validate the oldpassword filed.
Is there any possible way to acheive this.Hope Someone can help..
If you want to modify the validation methods for a field, you can use the Symfony FormEvents
With this you could add an EventListener to your oldPassword field:
$builder
->add(
'oldPassword',
PasswordType::class,
array(
'label' => 'Current password',
'mapped' => false,
'required' => false,
'error_bubbling' => true,
// without constraint, so the form can be submitted without
)
)
->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) use ($options) {
// if plainPassword is set, then overwrite the form field with check
if (isset(($event->getData())['plainPassword'])) {
$form = $event->getForm();
$form->add(
'oldPassword',
TextType::class,
[
'label' => 'Current password',
'required' => true,
'constraints' => new UserPassword(
[
'message' => "Please enter user's current password",
]
),
]
);
}
}
);

Symfony "This value should not be blank." while submitting form

I'm trying to submit a user form. But the form does not contain all database fields because I want to set the password later (when submitting form).
But now I'm getting the error that the field 'password' should not be blank. Removing the form validation is not working and adding them as HiddenType is also not working.
I'm getting this error after the $form->isValid() check
EDIT:
FormBuilder
$form = $this->createFormBuilder($user)
->add('email', TextType::class, array(
'label' => 'Email adres',
'attr' => array(
'class' => 'input-field'
)
))
->add('first_name', TextType::class, array(
'attr' => array(
'label' => 'Voornaam',
'class' => 'input-field'
)
))
->add('middle_name', TextType::class, array(
'label' => 'Tussenvoegsel (optioneel)',
'required' => false,
'attr' => array(
'class' => 'input-field'
)
))
->add('last_name', TextType::class, array(
'label' => 'Achternaam',
'attr' => array(
'class' => 'input-field'
)
))
->add('date_of_birth', DateType::class, array(
'label' => 'Geboortedatum',
'attr' => array(
'class' => 'input-field'
)
))
->add('save', SubmitType::class, array(
'label' => 'Gebruiker Opslaan',
'attr' => array(
'class' => 'form-submit-btn'
)
))
->getForm();
Submitting the form:
$form->handleRequest($request);
if($form->isSubmitted()){
if($form->isValid()){
dump($user);
exit;
}
}
EDIT:
Doing $user->setPassword('123'); before validation is not working
Can you post your User Entity?
You may get the Error because an argument is not allowed to be blank.
Take a look at any #Assert\NotBlank or nullable = false Annotations.
Can you show your User class? May be implements UserInterface? So, there is this validation.

Symfony form validate An invalid form control with name='' is not focusable

I have for with fields type entity, drop down choice and required true but when submit form have error in console
An invalid form control with name='inbound_invoice_row[costObject]' is not focusable.
new:1 An invalid form control with name='inbound_invoice_row[accountingAccount]' is not focusable.
new:1 An invalid form control with name='inbound_invoice_row[user]' is not focusable.
Another field validate fine, like vat or price but for accountingAccount user costObject have this error in console
why not understand
my form
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('location', EntityType::class, [
'class' => Location::class,
'empty_value' => 'select_default_value',
'query_builder' => self::getLocations(),
'required' => false,
'label' => 'locations',
'translation_domain' => 'invoicing'
])
->add('costObject', EntityType::class, [
'class' => CostObject::class,
'empty_value' => 'select_default_value',
'choices' => self::getCostObjectHierarchy(),
'required' => true,
'label' => 'cost_object',
'translation_domain' => 'invoicing'
])
->add('accountingAccount', EntityType::class, [
'class' => AccountingAccount::class,
'empty_value' => 'select_default_value',
'query_builder' => self::getAccountingAccount(),
'required' => true,
'label' => 'accounting_account',
'translation_domain' => 'invoicing'
])
->add('user', EntityType::class, [
'class' => User::class,
'empty_value' => 'select_default_value',
'choices' => self::getR(),
'required' => true,
'label' => 'employee',
'translation_domain' => 'invoicing'
])
->add('description', TextType::class, [
'label' => 'description',
'required' => false,
'translation_domain' => 'invoicing'
])
->add('vat', ChoiceType::class, [
'choices' => $this->vatClasses,
'required' => true,
'label' => 'vat',
'translation_domain' => 'common'
])
->add('price', TextType::class, [
'label' => 'price',
'required' => true,
'translation_domain' => 'invoicing'
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'EconomyBundle\Entity\InboundInvoiceRow',
'locations' => [],
'employees' => [],
'accounts' => [],
'vat' => [],
'cost' => [],
'ajax' => true,
'csrf_protection' => true
));
}
public function getName()
{
return 'inbound_invoice_row';
}
create form in action
$form = $this->createForm(
$this->get('economy.form.type.in_bound_invoice_row'),
$inboundInvoiceRow,
[
'validation_groups' => [InboundInvoiceRow::GROUP_POST],
'cascade_validation' => true,
'action' => $this->generateUrl('inbound_invoices_row_create', ['id' => $inboundInvoice->getId()]),
'method' => 'POST',
]
);
$form->add('submit', 'submit', array('label' => 'save', 'translation_domain' => 'invoicing'));
You probably have some js library that is used when rendering those fields (e.g. Select2 or Chosen). When there's some HTML validation error (e.g. the field is required but there is no value) on a field, but it's not visible - it might have display property set to none - then the browser is unable to attach error message to that field. This is what most likely triggers your error.
Simplest solution is to set 'required' => false in form type options and rely on backend validation (e.g. using Symfony Validation component) rather than on basic HTML validation.

Display a field depending on a checkbox

I have a form with a checkbox field and I want basically when this checkbox is checked, display a field below.
I do not know what is the best practice in symfony to do that...
Form builder :
public function buildForm(FormBuilderInterface $builder, array $options)
{
->add('protected', 'checkbox', array(
'label' => 'Protected by a password ?',
'required' => false,
'mapped' => false
))
->add('password', 'password', array(
'label' => 'Mot de passe',
'required' => false
))
}
So if the field 'protected' is checked, I want to display 'password' field.
I would do this with jquery.
Hide the field with css ( i would suggest to do this with a class instead)
->add('password', 'password', array(
'label' => 'Mot de passe',
'required' => false,
'attr' => array('style' => 'display:none;')
))
and then in jquery:
$('#checkbox_id').click(function() {
$('.password_id')[this.checked ? "show" : "hide"]();
});
I hope this helped!

Categories