How can I validate (server side) a form created and submitted from Vue?
I have a simple contact form, and I want to validate it both client and server side. For the client mode I'm using vee-validate (and it works), for the server side mode I want to use the Symfony's form validation.
So in this "special" case, the form is not rendered using the methods from Symfony, the form in this case is used only for validation. I have already created the form (it isn't linked to an Entity object), but when I send a http post request from my Vue's component, the validation on server side doesn't work. Seems that doesn't "read" the constraints created in the FormTypeclass.
My function:
public function sendContactUsEmailAction(Request $request, Mailer $mailer, TranslatorInterface $translator)
{
try {
$form = $this->createForm(ContactUsType::class);
$form->submit($request->request->all());
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
$emailParams = new ContactUsParams();
$emailParams->setName($data['name']);
$emailParams->setEmail($data['email']);
$emailParams->setSubject($data['subject']);
$emailParams->setMessage($data['message']);
$email = new ContactUsMail($mailer, $emailParams);
$email->send();
return new JsonResponse($translator->trans('send_contact_us_email_response'));
}
return new JsonResponse($this->getFirstFormError($form), 400);
} catch (\Exception $e) {
return new JsonResponse($e->getMessage(), 500);
}
}
Edited
This is the dump of my request data:
array:4 [
"name" => "Example"
"email" => "foo#example.com"
"subject" => "Test subject"
"message" => "Test message"
]
This is my FormType class. To test it I added for the name property the constraint "Email", but from the request I didn't send a valid email.
class ContactUsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, array(
'constraints' => array(
new NotBlank(),
new Email(),
),
))
->add('email', EmailType::class, array(
'constraints' => array(
new NotBlank(),
new Email(),
),
))
->add('subject', TextType::class, array(
'constraints' => new NotBlank(),
))
->add('message', TextareaType::class, array(
'constraints' => new NotBlank(),
));
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
'validation_groups' => false,
]);
}
}
I've found the solution. The problem was caused by the option "validation_groups" setted to false. I removed it and now works!
Related
Good day! Just started learning Symfony on my own.
I'm making a news portal. The administrator can download news from an Excel file. I am converting a file to an associative array. For example:
[ 'Title' => 'Some title',
'Text' => 'Some text',
'User' => 'example#example.com',
'Image' => 'https://loremflickr.com/640/360'
]
Next, I want to send this array to the form and use 'constraints' to validate it.
There are no problems with the fields 'Title', 'Text', 'Image'. I don't know how to properly check the 'User' field. The user in the file is submitting an Email, but I want to check that a user with that Email exists in the database.
NewsImportType
class NewsImportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Length(['min' => 256])
],
])
->add('text', TextareaType::class, [
'constraints' =>
[
new NotBlank(),
new Length(['max' => 1000])
],
])
->add('user', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Email(),
],
->add('image', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Length(['max' => 256]),
new Url()
],
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'allow_extra_fields' => true,
'data_class' => News::class,
]);
}
}
The entities User and News are connected by a One-to-Many relationship.
I was thinking about using ChoiceType and calling UserRepository somehow, but I don't understand how to apply it correctly.
Please tell me how to correctly write 'constraint' for the 'user' field.
thank!
Create a Custom Constraint. This way it is reusable in any other form you would like to check for a user.
Create a new folder in your project src/Validator then put these 2 files in there.
The Constraint
// src/Validator/userAccountExists.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
class UserAccountExists extends Constraint
{
public $message = 'User account does\'t exists. Please check the email address and try again.';
}
The Validator
// src/Validator/userAccountExistsValidator.php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
class UserAccountExistsValidator extends ConstraintValidator
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function validate($email, Constraint $constraint)
{
if (!$constraint instanceof UserAccountExists) {
throw new UnexpectedTypeException($constraint, UserAccountExists::class);
}
if (null === $email || '' === $email) {
return;
}
if (!is_string($email)) {
throw new UnexpectedValueException($email, 'string');
}
if (!$this->userExists($email)) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
private function userExists(string $email): bool
{
$user = $this->entityManager->getRepository(User::class)->findOneBy(array('email' => $email));
return null !== $user;
}
}
In your form you can now use the validator
->add('user', TextType::class, [
'constraints' =>
[
new NotBlank(),
new Email(),
new UserAccountExists(),
],
Remember to add use App\Validator\UserAccountExists; to your form.
I'm trying to validate my form by using $form->isValid(). But even if my form is correct it returns false. I already tried to dump my errors with $form->getErrors(true) but then my request times out.
My CreateController.php:
class CreateController extends Controller
{
/**
* #Method({"POST"})
* #Route("/api/v1/matches", name="api_v1_matches_create")
*/
public function index(Request $request, EntityManagerInterface $em): JsonResponse
{
$data = json_decode($request->getContent(), true);
$match = new Match();
$form = $this->createForm(MatchFormType::class, $match);
$form->submit($data);
if ($form->isValid()) {
$em->persist($match);
$em->flush();
return new JsonResponse(null, 201);
} else {
return new JsonResponse(null, 400);
}
}
}
My Form.php
class MatchFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'heroes',
EntityType::class,
[
'class' => Hero::class,
]
)
->add(
'season',
EntityType::class,
[
'class' => Season::class,
]
)
->add(
'map',
EntityType::class,
[
'class' => Map::class,
]
);
}
public function getName(): string
{
return 'match';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Match::class,
]);
}
}
JSON to POST
{
"map": 1,
"heroes": [
1,
2
],
"season": 1
}
Thanks in advance for your help!
I fixed it by adding 'multiple' => true to my heroes entry, so the form knows it's an array and by disable CSRF protection ('csrf_protection' => false as parameter in $resolver).
I believe you may want to follow the practices described in documentation here https://symfony.com/doc/4.1/forms.html#handling-form-submissions
Dividing article example into steps in your case you may want:
Create a new form object
Handle the http request
Check that form is being submitted & is valid
If previous condition is met, get form data & flush it to the db
I'm beginner on symfony and I have to use the collection form to make a site to reserve tickets for a show.
The interface is simple, The user select the number of tickets he wants, then it display as much form prototype (name and surname input) as tickets required.
This part works well for me.
I have 2 table "reservations" and "ticket" in mysql and I would like to get this render after submiting the form:
Table reservation :
Reservation id = 1
Table ticket:
Ticket id="1" name="thomas", surname="good", reservation_id="1"
Ticket id="2" name="laura", surname="senior", reservation_id="1"
But when I submit my form, I get +1 reservation in my table but no data in my ticket table.
class AdvertController extends Controller
{
public function formAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$reservation = new Reservations();
$form = $this->createForm(new ReservationsType(), $reservation, array(
'action' => $this->generateUrl('louvre_pages_homepage'),
'method' => 'POST',
));
$form->handleRequest($request);
$em->persist($reservation);
$em->flush();
return $this->render('LouvrePagesBundle:Pages:index.html.twig', array(
'form' => $form->createView(),
));
}
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('tickets', CollectionType::class, array(
'entry_type' => TicketType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype' => true,
)
)
->add('save', 'submit', array(
'attr' => array(
'class' => 'btn btn-lg btn-success'
)
));
}
public function getName()
{
return 'reservations';
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Louvre\PagesBundle\Entity\Reservations',
));
}
Is my controller right ?
I see 2 problems in the formAction:
there is no call to $em->flush(); anywhere
you are always calling $em->persist($reservation); regardless of the form validation state
Try doing it this way:
if ($form->isSubmitted() && $form->isValid()) {
$em->persist($reservation);
$em->flush();
// now redirect, add flash message, etc.
}
See http://symfony.com/doc/current/book/forms.html#handling-form-submissions
I am simply trying to add an email constraint to the unmapped field below and for some reason in the controller action it isn't picking it up. Is there something else I need to be doing?
Form Class
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', null, [
'label' => false,
'attr' => ['placeholder' => 'Name*']
]);
$builder->add('email', null, [
'mapped' => false,
'label' => false,
'attr' => ['placeholder' => 'Email*'],
'constraints' => [
new Email(["message" => "Please enter a valid Email Address"])
]
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Test\AppBundle\Entity\Feedback',
'cascade_validation' => true,
'validation_groups' => function(Form $form)
{
$feedback = $form->getData(); /** #var Feedback $feedback */
if ($feedback->getType() == Feedback::Type_Feedback)
return Feedback::ValidationGroup_Feedback;
else if ($feedback->getType() == Feedback::Type_Link)
return Feedback::ValidationGroup_Link;
throw new \Exception("Couldn't generate a valid Validation Group for FeedbackForm.php");
},
));
}
/**
* #return string
*/
public function getName()
{
return 'site_feedback_form';
}
Controller action
public function feedbackSiteSubmitAction(Request $request)
{
// The form award
$feedback = new Feedback();
// Create the form so we can bind send form values to it
$form = $this->createNewFeedbackForm($feedback);
// Bind form values
$form->handleRequest($request);
// Save
if ($form->isValid())
{
// Add the submission to a member
$email = $form->get('email')->getData();
$email = strtolower($email);
$member = $this->getMemberRepository()->loadByEmail($email);
if (!$member)
$member = $this->generateNewPersistedMember($email);
// Update the mail property
$member->setReceiveEmail(!$form->get('dontReceiveAlerts')->getData());
// Add the feedback to the member
$feedback->setMember($member);
// Persist the Feedback
$this->getEntityManager()->persist($feedback);
// Commit
$this->getEntityManager()->flush();
// Response
return $this->jsonSuccess([
'html' => $this->renderView('TestAppBundle:Site/partials:feedback_form_success.html.twig', [
'typeString' => $feedback->getType() == Feedback::Type_Feedback ? "Feedback" : "Resource suggestion"
])
]);
}
// Return errors
return $this->jsonError($this->getAllFormErrors($form));
}
In Symfony 2.8 upwards you do the following:
$builder->add('email', Symfony\Component\Form\Extension\Core\Type\EmailType::class);
Versions before that use:
$builder->add('email', 'email');
Then you add all your validation criteria via annotations in your Feedback class. See the options for EmailType here.
I have a form that collects data from two related entities 'Jobs' and 'Scope'. Jobs data adds successfully to the database, but i can't get the scope entity to add which also requires the unique ID that was created from the jobs entry. I'm using doctrine by the way.
Here is jobsType:
class jobsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('jobNumber', null, array('label' => 'Job Number','attr' => array('placeholder'=>'Code used for job')))
->add('description', null, array('label' => 'Job Description',))
;
$builder->add('scopes', new scopeType())
;
}
public function getName()
{
return 'jobs';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Baker\TimeSlipBundle\Entity\Jobs',
));
}
}
Here is scopeType
class scopeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('description', 'entity', array('label' => 'Scope', 'required' => false,
'class' => 'BakerTimeSlipBundle:Scope',
'query_builder' => function(EntityRepository $er){
return $er->createQueryBuilder('s')
->orderBy('s.description', 'asc');
},
'property' => 'description',
'multiple' => false,
'expanded' => false,
'empty_value' => false,
'attr' => array('class' => 'form-inline'),
'label_attr' => array('class' => 'required')
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Baker\TimeSlipBundle\Entity\Scope',
));
}
public function getName()
{
return 'scope';
}
}
Here is my controller, i think the problem here is that i'm not passing the scope() instance to the form builder. Which i'm not sure how to do. The entity is mapped correctly.
class JobsController extends Controller
{
public function indexAction($limit, Request $request)
{
$jobs= new Jobs();
$scope = new Scope();
$fname=$limit;
$form = $this->createForm(new JobsType(),$jobs, array(
'method' => 'POST',
));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($jobs);
$em->persist($scope);
$em->flush();
return $this->redirectToRoute('company',array ('limit' =>'Job Submitted Successfully'));
}
return $this->render(
'BakerTimeSlipBundle:Jobs:index.html.twig',
array('fname' => $fname,
'form' => $form->createView()
)
);
}
}
}
Any help here would be greatly appreciated. Below is listed the error message when I try to enter the scope:
An exception occurred while executing 'INSERT INTO Scope (description, jobsId, jobsid) VALUES (?, ?, ?)' with params [null, null, null]:
SQLSTATE[42000]: Syntax error or access violation: 1110 Column 'jobsId' specified twice
The problem here is the $jobs object you link to your form has no relation with the $scope object. So when you try to persist $jobs, Doctrine has no idea $scope it had to persist.
Just try this :
$jobs= new Jobs();
$scope = new Scope();
$jobs->setScope($scope);
Then, in your Jobs entity, on the annotation above $scopes, make sure you have cascade={"persist"} (but you must already have it because Doctrine tries to persist, and that's where the error is thrown), and you should be good to go.
Hope this helps