Symfony entity constraints not respected on form used with ajax patch request - ajax

On my symfony 6 project, I try to update a data with an Ajax request. First, I display an input tag with stimulus.js and then with an eventlistener I use the ajax request to handle and submit a form with the new value to check if it respect my entity asserts constraints. I try to update on property ('signature') from on Entity ('CompteClient').
My problem is that my constroller doesn't check asserts (not blank and length between 5 and 50) from my entity and always persist datas, even if it's not correct.
I already try to add constraint on my formBuilderType, but I always add the same result.
I used principally this page from documentation https://symfony.com/doc/current/form/direct_submit.html
Here's, part of my code :
-> in js, replace input by span tag while doing update :
afficherSpan(id, propriete){
let aModifier = document.getElementById(propriete+'-'+id)
let inputToDisplay = document.getElementById('input-'+propriete+'-'+id)
let contenu = inputToDisplay.value
this.enregistrer(id, propriete, contenu)
//Span
let spanToDisplay = document.createElement('span')
spanToDisplay.setAttribute('id', 'span-'+propriete+'-'+id)
spanToDisplay.innerText = contenu
//Replace
aModifier.replaceChild(spanToDisplay, inputToDisplay)
}
enregistrer(id, propriete, contenu){
fetch("compte/"+id+"/modifier/"+propriete+"/"+contenu,
{method: 'PATCH'}
)
.then(res => res.json())
.then(data => console.log(data))
.then(function() {
})
.catch(err => console.log(err))
}
-> my formBuilder :
class SignatureType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('signature', TextType::class, [
'required' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => CompteClient::class,
'validation_groups' => ['app_compte_client']
]);
}
}
-> my property 'signature' from the CompteClient entity :
#[ORM\Column(type: 'string', length: 50)]
#[Groups(["CompteClient:item:post", "CompteClient:signature:patch"])]
#[Assert\NotBlank(message:'Veuillez renseigner une signature.', groups:["app_compte_client"])]
#[Assert\Length(min:5, max:50, minMessage:'Veuillez renseigner une signature valide (> ou = à 5 car.).', maxMessage:'Veuillez renseigner une signature valide (< ou = à 50 car.).', groups:["app_compte_client"])]
private $signature;
-> then my controller :
#[Route('/compte/{id}/modifier/{propriete}/{contenu}', name: 'comptes_modifier', methods:['PATCH'])]
public function modifier(CompteClientRepository $compteClientRepository, EntityManagerInterface $entityManager, Request $request, int $id, string $propriete, string $contenu): Response
{
$compteClient = $compteClientRepository->find($id);
try {
if($compteClient){
if($propriete == 'signature' && !empty($contenu)){
$form = $this->createForm(SignatureType::class, $compteClient);
if($request->isMethod("PATCH")){
$form->get('signature')->submit($contenu, false);
if($form->get('signature')->isSubmitted() && $form->get('signature')->isValid()){
$compteClient->setSignature($contenu);
$entityManager->persist($compteClient);
$entityManager->flush();
return new Response(
json_encode([
'message'=> $propriete.' du compte de '.$compteClient->getNomCompte().' mis à jour avec succès'
]),202
);
}else{
$errors = $form->getErrors(true, false);
return new Response(
json_encode([
'message'=> $errors
]),406
);
};
}
}
}
} catch (\Throwable $th) {
return new Response(
json_encode([
$th
]),500);
}
}
So, even if I try to update signature with a string with length = 2, It's update in the db.
Someone have an idea ?

Related

How to flash validation errors to session in Laravel

The built in behavior for flashing back validation errors in Laravel does not seem to be working for my use case.
I have a (React) form that posts it's data via fetch API using this method, which reloads or redirects the page with (hopefully) any session data after the response is returned:
fetch(props.register_route, {
method: 'POST',
headers: {
'X-CSRF-Token': props.csrf,
},
body: data,
})
.then((result) => {
return result.json();
})
.then((result) => {
console.log(result);
window.location.href = result.url;
},
(error) => {
console.log(error);
});
In my controller, I validate this data but if I structure it as follows, the errors are not available as $errors in the resulting page
if ($validator->fails()) {
return redirect()->back()->withErrors($validator);
}
However if I manually flash the errors to the session and return a url instead of a redirect, suddenly the behavior works.
if ($validator->fails()) {
Session::flash('errors', $validator->errors());
return response->json([
'url' => route('register'),
], Response::HTTP_NOT_ACCEPTABLE);
}
I feel as if I must be doing something incorrectly here to have to use this workaround. I could also manually send the errors back in the response, which may be the right way to structure things in the long run.
when you are calling api from javascript or front end applications like Reactjs,Angular,android etc.. .So it expect return result should be in json format so it should be like
if ($validator->fails()) {
return response()->json( $validator->errors(),422);
}
if you not calling Method from direct laravel blade then pass response in JOSN Format.
like
https://laravel.com/docs/8.x/responses#json-responses
Or
make one ResponseManager File
<?PHP
namespace App\Libraries\utils;
class ResponseManager {
public static $response = array('flag' => true, 'data' => '', 'message' => '', 'code' => 01,);
public static function getError($data = '', $code = 10, $message = '', $flag = false) {
self::$response['flag'] = $flag;
self::$response['code'] = $code;
self::$response['data'] = $data;
self::$response['message'] = $message;
return self::$response;
}
public static function getResult($data = '', $code = 10, $message = '', $flag = true) {
self::$response['flag'] = $flag;
self::$response['code'] = $code;
self::$response['data'] = $data;
self::$response['message'] = $message;
return self::$response;
}}
Define in config/app.php
//custom class
'ResponseManager' => App\Libraries\utils\ResponseManager::class,
and then use in whole project
Error Message Like
if ($validation->fails()) {
$message = $validation->messages()->first();
return Response()->json(ResponseManager::getError('', 1, $message));
}
Success Message Like
return Response()->json(ResponseManager::getResult(null, 10, "Success"));

Symfony multiple choice issue with entity type and form events

I got 3 entities works fine but there are problem at symfony form creating
// My FormType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$translator = $this->translator;
$builder
->add('Country', EntityType::class, array(
'class' => 'App\Entity\Countries',
'placeholder' => '',
))
->add('City', EntityType::class,array(
'class' =>'App\Entity\Cities'
))
;
$formModifier = function (FormInterface $form, Countries $sport = null) {
$positions = null === $sport ? array() : $sport->getCities();
$form->add('City', EntityType::class, array(
'class' => 'App\Entity\Cities',
'placeholder' => '',
'choices' => $positions,
));
};
$formModifier2 = function (FormInterface $form, Cities $sport = null) {
$positions = null === $sport ? array() : $sport->getCountryId();
$form->add('District', EntityType::class, array(
'class' => 'App\Entity\Districts',
'placeholder' => '',
'choices' => $positions,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getCountry());
}
);
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier2) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier2($event->getForm(), $data->getCity());
}
);
$builder->get('Country')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$sport = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $sport);
}
);
$builder->get('City')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier2) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$sport = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier2($event->getForm()->getParent()->getParent(), $sport);
}
);
}
// this my controller
public function userAddress(UserInterface $user,Request $request,HelperService $helper, MailGenerator $mail,TranslatorInterface $translator)
{
$user_address = new UserAddresses();
$form = $this->createForm(NewAddressFormType::class,$user_address,array('csrf_protection' => false));
$form->handleRequest($request);
if($form->isSubmitted()) {
$data = $form->getData();
}
return $this->output('checkout/checkout_address',array('form'=>$form->createView()));
}
Form rendering ok i change my first select box and working ajax post but second one first but third one is not working not send ajax data
{% block script%}
<script>
var $sport = $('#new_address_form_Country');
// When sport gets selected ...
$sport.change(function() {
// ... retrieve the corresponding form.
var $form = $('#new_address_form').closest('form');
// Simulate form data, but only include the selected sport value.
var data = {};
data[$sport.attr('name')] = $sport.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current position field ...
$('#new_address_form_City').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#new_address_form_City')
);
}
});
});
var $sport2 = $('#new_address_form_City');
// When sport gets selected ...
$sport2.change(function() {
// ... retrieve the corresponding form.
var $form = $('#new_address_form').closest('form');
// Simulate form data, but only include the selected sport value.
var data = {};
data[$sport2.attr('name')] = $sport2.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current position field ...
$('#new_address_form_District').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#new_address_form_District')
);
}
});
});
</script>
{% endblock %}
You should use $builder->addEventListener. For multiple fields all you need to do is to have dynamic fields inside FormEvents::PRE_SET_DATA event handler. Also, use parent field data, as explained in the doc to fetch child field choices.
I have used this approach, for generating Country, City and District Entities in hierarchical fields. Let me know if it helps or you need more information.
$builder->add(); // all other fields..
$builder->addEventSubscriber(new DynamicFieldsSubscriber());
Create an eventSubscriber as defined in the doc, here the file name is DynamicFieldsSubscriber
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmitData',
);
}
/**
* Handling form fields before form renders.
* #param FormEvent $event
*/
public function preSetData(FormEvent $event)
{
$translator = $this->translator;
$location = $event->getData();
// Location is the main entity which is obviously form's (data_class)
$form = $event->getForm();
$country = "";
$city = "";
$district = "";
if ($location) {
// collect preliminary values for 3 fields.
$country = $location->getCountry();
$city = $location->getCity();
$district = $location->getDistrict();
}
// Add country field as its static.
$form->add('country', EntityType::class, array(
'class' => 'App\Entity\Countries',
'label' => $translator->trans('country'),
'placeholder' => $translator->trans('select'),
'required' => true,
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c');
}
));
// Now add all child fields.
$this->addCityField($form, $country);
$this->addDistrictField($form, $city);
}
/**
* Handling Form fields before form submits.
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
// Here $data will be in array format.
// Add property field if parent entity data is available.
$country = isset($data['country']) ? $data['country'] : null;
$city = isset($data['city']) ? $data['city'] : null;
$district = isset($data['district']) ? $data['district'] : null;
// Call methods to add child fields.
$this->addCityField($form, $country);
$this->addDistrictField($form, $city);
}
/**
* Method to Add City Field. (first dynamic field.)
* #param FormInterface $form
* #param type $country
*/
private function addCityField(FormInterface $form, $country = null)
{
$translator = $this->translator;
$countryCode = (is_object($country)) ? $country->getId() : $country;
// $countryCode is dynamic here, collected from the event based data flow.
$form->add('city', EntityType::class, array(
'class' => 'App\Entity\Cities',
'label' => $translator->trans('city'),
'placeholder' => $translator->trans('select'),
'required' => true,
'attr' => array(
'class' => 'col-md-12 validate-required',
'placeholder' => 'City *'
),
'query_builder' => function (EntityRepository $er) use($countryCode) {
return $er->createQueryBuilder('p')
->where('p.country_id = :country')
->setParameter('country', $countryCode);
}
));
}
/**
* Method to Add District Field, (second dynamic field)
* #param FormInterface $form
* #param type $city
*/
private function addDistrictField(FormInterface $form, $city = null)
{
$translator = $this->translator;
$cityCode = (is_object($city)) ? $city->getId() : $city;
// $cityCode is dynamic in here collected from event based data flow.
$form->add('district', EntityType::class, array(
'class' => 'App\Entity\Districts',
'label' => $translator->trans('district'),
'placeholder' => $translator->trans('select'),
'required' => true,
'attr' => array('class' => 'district'),
'query_builder' => function (EntityRepository $er) use($cityCode) {
return $er->createQueryBuilder('s')
->where('s.city = :city')
->setParameter('city', $cityCode);
}
));
}
And your project JQuery should look like this
<script>
var $sport = $('#new_address_form_country');
// When sport gets selected ...
$sport.change(function() {
// ... retrieve the corresponding form.
var $form = $('#new_address_form').closest('form');
// Simulate form data, but only include the selected sport value.
var data = {};
data[$sport.attr('name')] = $sport.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current position field ...
$('#new_address_form_city').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#new_address_form_city')
);
}
});
});
$(document).on('change','#new_address_form_city',function () {
// ... retrieve the corresponding form.
var $form2 = $('#new_address_form').closest('form');
// Simulate form data, but only include the selected sport value.
var data2 = {};
data2[$('#new_address_form_city').attr('name')] = $('#new_address_form_city').val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form2.attr('action'),
type: $form2.attr('method'),
data : data2,
success: function(html) {
console.log($(html).find('#new_address_form_district'));
// Replace current position field ...
$('#new_address_form_district').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#new_address_form_district')
);
}
});
});
</script>
I hope this will help.
I believe that sometimes ajax call after ajax call doesn't work as it looses the contact with jquery event listeners.
Create a function for $sport2.change event listener and name it something e.g. sport2Change. And try to call that function on an onchange event of the the element with id - new_address_form_City

How to reinitialize model when client side validation fails in Yii 2?

I am working on Yii 2 form and I want to reinitialize model when client side validation fails. For example with certain rules like below:
public function rules()
{
return [
[['username'], 'required', 'message' => 'You must enter your username'],
['username','email'],
[['password'], 'required', 'message' => 'You must enter your password'],
];
}
When validation fails I want all fields to be empty (for example when user enters invalid email address). How can I do that?
I assume you use standard Yii 2 way of loading the model:
$model = new SomeModel();
if ($model->load(\Yii::$app->request->post()) && $model->save()) {
// ...
}
return $this->render('view', ['model' => $model]);
Set fields to null when validation fails. You don't want to create new instance (which would be easier) because you would lost all validation messages.
$model = new SomeModel();
if ($model->load(\Yii::$app->request->post())) {
if ($model->save()) {
// ....
} else {
$model->username = null;
$model->password = null;
}
}
return $this->render('view', ['model' => $model]);
UPDATE: for the client side validation add this JS code in view:
$("#form-ID").on("afterValidateAttribute", function (event, attribute, messages) {
if (event.result === false) {
attribute.value = "";
}
});
Replace #form-ID with proper form element JS identifier.

Sf2 : FOS UserBundle : registration AJAX

I'm trying to register a user with AJAX.
I created an event listener on FOSUserEvents::REGISTRATION_SUCCESS
So I'm trying to know is an AJAX request has been made but the response on my client side doesn't satisfy me.
Here my event listener, note that the response sent is a test so of course there should be no "else" condition.
<?php
namespace SE\AppBundle\EventListener;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Ajax listener on FOS UserBundle registration
*/
class RegistrationListener implements EventSubscriberInterface
{
private $router;
public function __construct(RequestStack $RequestStack)
{
$this->requestStack = $RequestStack;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess'
);
}
public function onRegistrationSuccess()
{
$request = $this->requestStack->getCurrentRequest();
if ($request->isXmlHttpRequest()) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
}
else{
$array = array( 'success' => false ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
return $response;
}
}
}
services.yml:
se.app.listener.registration:
class: SE\AppBundle\EventListener\RegistrationListener
arguments: ["#request_stack"]
tags:
- { name: kernel.event_subscriber }
javascript:
// Submit the request
$.ajax({
type : 'POST',
url : url,
data : data,
success : function(data, status, object) {
console.log('success');
console.log(data);
},
error: function(data, status, object){
console.log('error');
console.log(data);
}
});
Firstly the weird thing is that it goes in the error condition.
The console.log (data) is returned the DOM of the registration success page :
...
<p>Congrats brieuc.tribouillet7777#gmail.com, your account is now activated.</p>
...
So does this logic should be here or should I override the controller? What am I doing wrong?
Because of the level of the REGISTRATION_SUCCESS event, you can't return a response directly from the EventListener.
You need to grab the FormEvent and modify its response.
Lets pass it as argument:
class RegistrationListener implements EventSubscriberInterface
{
// ...
public function onRegistrationSuccess(FormEvent $event)
{
$request = $this->requestStack->getCurrentRequest();
// Prepare your response
if ($request->isXmlHttpRequest()) {
$array = array( 'success' => true ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
} else {
$array = array( 'success' => false ); // data to return via JSON
$response = new Response( json_encode( $array ) );
$response->headers->set( 'Content-Type', 'application/json' );
}
// Send it
$event->setResponse($response);
}
}
And it should work.
Note There is an issue about this event where the response cannot be modified.
If the problem occurs, you need to set a low priority in your event subscribing:
public static function getSubscribedEvents()
{
return [
FOSUserEvents::REGISTRATION_SUCCESS => [
['onRegistrationSuccess', -10],
],
];
}
See #1799.
EDIT
Note You should use a JsonResponse instead of json_encode your data and set the Content-Type manually.
To grab the form itself and its eventual errors, you can do this:
public function onRegistrationSuccess(FormEvent $event)
{
$form = $event->getForm();
if (count($validationErrors = $form->getErrors()) == 0) {
return $event->setResponse(new JsonResponse(['success' => true]));
}
// There is some errors, prepare a failure response
$body = [];
// Add the errors in your response body
foreach ($validationErrors as $error) {
$body[] = [
'property' => $error->getPropertyPath() // The field
'message' => $error->getMessage() // The error message
];
}
// Set the status to Bad Request in order to grab it in front (i.e $.ajax({ ...}).error(...))
$response = new JsonResponse($body, 400);
$event->setResponse($response);
}
But because it's a success event, you may need to override the method itself.

How to ajaxify Zend_Form validation

I have a Zend_Form subclass. Some elements are set to belong to arrays.
class My_Form extends Zend_Form {
public function __construct() {
$elem = $this->createElement('text','PROJECT_NAME',
array(
'required' => true
));
$elem->setBelongsTo('project');
$this->addElement($elem);
$elem = $this->createElement(
'text','PLANNED_END_DATE',
array(
'required' => true
)
);
$elem->setBelongsTo('project');
$elem->addValidator(new Zend_Validate_Date(array('format'=>'yyyy-MM-dd')));
$this->addElement($elem);
//and so on
}
}
I have a universal validation controller which does create the form and checks for errors, and returns them in json format:
class ValidateController extends Zend_Controller_Action
{
public function formAction()
{
$params = $this->_getAllParams();
if (isset($params['_frm'])) {
$formName = detect_the_form_class($params['_frm']);
if (class_exists($formName)) {
$form = new $formName();
if ($form instanceof Zend_Form) {
$result = $form->isValidPartial($params);
$messages = $form->getMessages();
$this->getResponse()
->setHeader('Content-Type', 'application/json')
->setBody(json_encode(array(
'result' => $result,
'messages' => $messages
)));
} else {
$this->getResponse()->setHttpResponseCode(500);
}
}
}
}
}
This controller works great for non-array forms, but the form I now need to validate hase arrays, eg elements with name 'project[PROJECT_NAME]'.
But the $form->getMessages() returns messages indexed with base name of elements, without array prefix.
The actual result is:
{ result: false,
messages: {
PROJECT_NAME: {isEmpty: "Value is required"},
PROJECT_END_DATE: {isEmpty: "Value is required"}
}
}
The result I need is:
{ result: false,
messages: {
"project[PROJECT_NAME]": {isEmpty: "Value is required"},
"project[PROJECT_END_DATE]": {isEmpty: "Value is required"}
}
}
or something similar, so I can find the element the validation message is for.
Any ideas?
For subforms use Zend_Form_Subform class:
class My_Form extends Zend_Form
{
public function init()
{
$subForm = new Zend_Form_SubForm();
$elem = $this->createElement('text', 'PROJECT_NAME', array(
'required' => true
));
$subForm->addElement($elem);
$elem = $this->createElement('text', 'PLANNED_END_DATE', array(
'required' => true
));
$subForm->addElement($elem);
$this->addSubForm($subForm, 'project');
}
}
Response:
{
"project":{
"PROJECT_NAME":{"isEmpty":"Value is required and can't be empty"},
"PLANNED_END_DATE":{"isEmpty":"Value is required and can't be empty"}
}
}
For form config it is recommended to use init() method.
For json response you can use build-in action helper:
$this->_helper->json($form->getMessages());

Resources