Symfony 4 - isValid always returns false - php

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

Related

Symfony form doesn't change value by using FormEvents::PRE_SUBMIT

Following problem I have: In Symfony (Version 4.4.22) I created a FormType with a date-field and a checkbox. If the checkbox was checked then the field should get the value of "31.12.9999".
If a requesting form has the value 1 for the field infiniteValidTo, the value of validTo should change from empty to "31.12.9999". (In my case the date field has the value 'null' when the form was submitted.)
So I added an EventListener to the form builder with a pre_submit hook that will add this info before the form is validating.
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('validTo', DateType::class, [
'required' => FALSE,
'format' => 'dd.MM.yyyy'
])
->add('infiniteValidTo', CheckboxType::class, [
'required' => FALSE
])
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
if (isset($data['infiniteValidTo']) && $data['infiniteValidTo'] === '1') {
$data['validTo'] = '31.12.9999';
}
$event->setData($data);
});
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => SettingFormModel::class,
'constraints' => [
new Callback([
'callback' => [$this, 'validateFormModel']
])
]
]);
}
/**
* #param SettingFormModel $object
* #param ExecutionContextInterface $context
*/
public function validateFormModel(SettingFormModel $object, ExecutionContextInterface $context): void {
dump($object);
}
Before leaving the listener method the data-array has the correct values (by dumping the variable).
For validating the form in a dynamical way, I defined a callback method for the data object. When the data container arrives the methods, my change of the validTo field is gone. If I change the field into a simple text field it works, but not for a date field.
After debugging a lot of time, I saw that the method mapFormsToData doesn't transform the change into the form object.
Do I made a mistake by configuration or is this a bug in symfony? Has somebody else the same issue with a form?
I found the mistake. The setter of SettingFormModel was not correct. After repairing the Listener works as it should.
You can use a post_submit event
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('validTo', DateType::class, [
'required' => FALSE
])
->add('infiniteValidTo', CheckboxType::class, [
'required' => FALSE
]);
$builder->get('infiniteValidTo')->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
if ($event->getForm()->getData()) {
$event->getForm->getParent()->getData()->setValidTo(new \DateTime('9999-12-31'));
}
});
}

How can I transfer parameters from function to function within one Controller in Symfony 4?

I am trying to pass a parameter from a FORM to a twig, that is different from the one where I make my data input. If this explanation is messy - here's more simplified version... I have just started to study php+symfony so please don't hurt me too hard...
I have two empty fields on a "CREATE" page,
I fill them with say AAA and BBB each,
I want AAA and BBB to appear on another "FORMZ" page.
I am NOT using any database, so no need to use ObjectManager etc, it's only to understand how everything's working...
I have created a ArticleFormType.php file
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'attr' => [
'placeholder' => "title from ArticleFormType",
'class' => 'form-control'
]
])
->add('content')
->add('save', SubmitType::class, [
'label' => 'SAVE'
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => MyClassArticle::class,
]);
}
and that is my Controller:
/**
* #Route("/formz", name="formz")
*/
public function index()
{
$title = 'Created by DT';
return $this->render('formz/index.html.twig', [
'title' => $title
]);
}
/**
* #Route("/formz/create", name="create")
*/
public function create (Request $request, ObjectManager $manager ) {
$article = new MyClassArticle();
$task = new ArticleFormType();
$form = $this->createForm(ArticleFormType::class, $article, [
'action' => $this->generateUrl('create'),
]);
$form->handleRequest($request);
dump($article);
if($form->isSubmitted() && $form->isValid()) {
dump($article);
return $this->redirectToRoute('formz');
}
return $this->render('formz/create.html.twig', [
'formTest' => $form->createView()
]);
}
I don't get how can I transfer my $article to a public function index() - I heard it can be done somehow (in the case above) by passing parameters from public function create() to public function index().
Can anybody help me with that please?
I am thanking you in advance!
you need to store the article somewhere, ideally through doctrine (entity save)
then you need to update your index route to be something like this
#Route("/formz/{article}", name="formz")
function index(Artile $article = null)
Then you can use the following with redirectToRoute
$this->redirectToRoute("formz", array("article" => $article))
That should solve your issue.

How to get data from the submitted form in Symfony3?

I faced up with some non-ordinary situation for me.
1) I have a dependent list that rendering by Symfony FormType like this:
2) Location and Instruction fields are depend from Company field.
3) When I change Company field (onchange event js) then goes ajax request that retrieves data from the database and build a dropdown list.
4) But when form is submitted I have an error:
Please help me to resolve this. Thanks in advance.
My formType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('hours')
->add('messageText', null, ['required' => false])
->add('company', null, [
'choice_label' => 'name',
'placeholder' => 'Please select company',
'required' => true
])
->add('procedure', TextType::class, ['attr' => ['placeholder' => 'Please type code or description'] ])
->add('instruction', ChoiceType::class, ['mapped' => false])
->add('location', ChoiceType::class, ['mapped' => false])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => InstructionNotification::class
));
}
Action from controller:
/**
* #Route("/admin/api/instructions", name="admin_api_instructions")
* #param Request $request
* #return JsonResponse
*/
public function getInstructionsByCompanyId(Request $request)
{
$id = $request->get('id');
if (!$id) {
return new JsonResponse('No data', 404);
}
$instructions = $this->getDoctrine()->getRepository('OctaneBundle:Instruction')->findInstructionsByCompanyId($id);
return new JsonResponse($instructions);
}
findInstructionsByCompanyId($id):
public function findInstructionsByCompanyId($id)
{
$qb = $this->createQueryBuilder('i');
if ($id) {
$qb
->where('i.company = :id')
->setParameter('id', $id);
}
return $qb->getQuery()->getResult();
}
response from api (i.e.: admin/api/instructions?id=1):
[{"id":2,"label":"First instruction"},{"id":3,"label":"First instruction"}]
If you need any additional information please leave comments below. Thanks
Symfony's Validator expects that your submitted form will have a submitted instruction and location value that exists in the list you provided when creating your form in form type class. Since you are not providing any options for instructions and locations, you are getting a validation error.
In order to bypass this error you should use Symfony's Form Events in your buildForm function in your form type like this:
$builder->get('company')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) {
$company = $event->getForm()->getData();
$form = $event->getForm()->getParent();
$form->add('location', EntityType::class, array(
'class' => Location::class,
'query_builder' => function (EntityRepository $repo) use ($company) {
return $repo->createQueryBuilder('location')
->where('location.company = :company')
->setParameter('company', $company->getId());
}
));
$form->add('instruction', EntityType::class, array(
'class' => Instruction::class,
'query_builder' => function (EntityRepository $repo) use ($company) {
return $repo->createQueryBuilder('instruction')
->where('instruction.company = :company')
->setParameter('company', $company->getId());
}
));
}
);
Thanks for the answer but I found out more elegant solution for my case. So,
my formType now:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
...
->add('instruction', FormattedSelectType::class, ['class' => Instruction::class])
->add('location', FormattedSelectType::class, ['class' => Location::class])
;
}
FormattedSelectType:
class FormattedSelectType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'choice_label' => function (FormattedLabelInterface $entity) {
return $entity->getFormattedLabel();
}
));
}
/**
* {#inheritdoc}
*/
public function getParent()
{
return EntityType::class;
}
}
Etities Location and Instruction entities implement JsonSerializable and custom FormattedLabelInterface interface and have the next methods:
/**
* #return string
*/
public function getFormattedLabel()
{
return sprintf(self::LABEL_FORMATTED, $this->zip, $this->city, $this->name, $this->address, $this->phone);
}
/**
* #return array|mixed
*/
public function jsonSerialize()
{
return [
'id' => $this->id,
'label' => $this->getFormattedLabel()
];
}

Symfony set the values for form that already created as a formtype

I have created a form type like below
/**
* Class CreatePosFormType.
*/
class CreatePosFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name', TextType::class, [
'required' => true,
'constraints' => [new NotBlank()],
]);
$builder->add('identifier', TextType::class, [
'required' => true,
'constraints' => [new NotBlank()],
]);
$builder->add('description', TextType::class, [
'required' => false,
]);
$location = $builder->create('location', LocationFormType::class, [
'constraints' => [new NotBlank(), new Valid()],
]);
$location->addModelTransformer(new LocationDataTransformer());
$builder->add($location);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Pos::class,
]);
}
}
In my controller I have get the form and send the request to the form as below:
$form = $this->get('form.factory')->createNamed('pos', CreatePosFormType::class);
$form->handleRequest($request);
But I need instead of sending the request to the form get the data from the request and set the values for individually I have tried like below:
$form->get('name')->setData('john');
But It's not setting the form field value.
If I set the values to form by above the below error occures
{
"form": {
"children": {
"name": {},
"identifier": {},
"description": {},
"location": {},
}
},
"errors": [] }
You can send the mapped class to the form itself. Like this:
public function createPost(Request $request)
{
$pos = new Pos();
$pos->setName('John');
$form = $this->get('form.factory')->createNamed('pos', CreatePosFormType::class, $pos);
}
You can also send in data through the options. Like this:
public function createPost(Request $request)
{
$form = $this->get('form.factory')->createNamed('pos', CreatePosFormType::class, null, ['yourVariable' => $yourVariable]);
}
And in the form class you would catch that in your options.
class CreatePosFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$yourVariable = $options['yourVariable'];
//do stuff with your variable
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Pos::class,
'yourVariable' => null,
]);
}

symfony 2 how to pass array collection to input select

Hi i am tying pass array collection (method getProjects() returns it) to form (select input) and fail. This code returns exception - A "__toString()" method was not found on the objects of type "Tasker\WebBundle\Entity\Project" passed to the choice field.
Can anybody help? Is needed transformer? Or what is right way?
Controller:
/**
* #Route("/pridaj", name="web.task.add")
* #Template()
*/
public function addAction(Request $request)
{
$task = new Task;
/** #var User $loggedUser */
$loggedUser = $this->get('security.token_storage')->getToken()->getUser();
$form = $this->createForm(new AddTaskType(), $task, ['user' => $loggedUser]);
if ($form->handleRequest($request) && $form->isValid()) {
// some stuff
}
return [
'form' => $form->createView()
];
}
Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('project', 'entity', [
'label' => 'Projekt:',
'class' => 'TaskerWebBundle:Project',
'choices' => $options['user']->getProjects(),
'placeholder' => 'Označte projekt',
])
// ....
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'user',
));
$resolver->setDefaults(array(
'user' => null,
));
}
just add __ToString() to your Project class
Tasker\WebBundle\Entity\Project
class Project
{
....
function __toString() {
return $this->getName(); //or whatever string you have
}
}
I wanted to add another answer, because you do not have to add __toString() to your Project class. The Symfony entity field type allows you to specify which property/field to use for displaying. So instead of __toString() you could specify the property in the form configuration like so:
$builder
->add('project', 'entity', [
'label' => 'Projekt:',
'class' => 'TaskerWebBundle:Project',
'choices' => $options['user']->getProjects(),
'placeholder' => 'Označte projekt',
'property' => 'name'
])
If you check this part of the Symfony documentation you will see that __toString() is automatically called only if you do not specify the property.

Categories