Not to validate the form in Symfony 2 - forms

Here is my form, I build in the controller:
$defaulData = ....
$formBuilder = $this->createFormBuilder($defaultData);
$entity_options = array(
'class' => 'MyBundle:Param',
'property' => 'description',
'query_builder' => function(EntityRepository $er) {
return $er->getParamsFromCategorieQueryBuilder(MyController::$category_for_list);
},
'label' => 'Donnée à afficher'
);
$formBuilder
->add('entity_types', 'entity', $entity_options);
The form is a list of Param objects, it displays good but for some reason, when I submit the form, I have an error on entity_types field saying that the value cannot be blank though there is one Param selected (even by default).
So I was wondering if I could disable validation.
When whould I put this validation_groups to false ? if it is in $entity_options, I tried it already and it does not work.
ty

You should modify you options like this (both require and groups are needed):
$entity_options = array(
'class' => 'MyBundle:Param',
'property' => 'description',
'query_builder' => function(EntityRepository $er) {
return $er->getParamsFromCategorieQueryBuilder(MyController::$category_for_list);
},
'required' => false,
'validation_groups' => false,
'label' => 'Donnée à afficher'
);
If the error is instead on the Form class itself, you should change in:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => false,
));
}

The way to prevent validation for good is this one :
$formBuilder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
$event->stopPropagation();
}, 900);
It prevents from calling the validation event.
The solution that giosh94mhz gave me what not good because even with validation_group = false some validation are still done.

Related

Same field multiple times Symfony 4 form

I have an Entity Report, which is holds many Answers.
class Report
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Answer", mappedBy="report")
*/
private $answers;
...
}
class Answer
{
/**
* #ORM\ManyToOne(targetEntity="Report", inversedBy="answers")
*/
private $report;
...
}
I add the questions manually in my ReportType (I know this is not the best aproach but the questions will never change and I'm only interested in the answers.)
class ReportAnswersType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('answers', ChoiceType::class, array(
'label' => 'Room state',
'multiple' => false, 'expanded' => false,
'choices' => array('OK' => 'OK', 'NG' => 'NG', 'NP' => 'NP',),
))
->add('answers', IntegerType::class, array(
'label' => 'Temperature',
))
->add('answers', ChoiceType::class, array(
'label' => 'Is it good?',
'multiple' => false, 'expanded' => false,
'choices' => array('YES' => 'YES', 'NO' => 'NO',),
))
...
}
->add('save', SubmitType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => Report::class,
));
}
What I would like this to do is to generate a Form with 3 questions and insert each of the answers in the database.
But the rendered form only shows the last question (because with each ->add('answers', ...) I replace the last one added).
I tried different solutions to solve this:
Following the documentation on How to Embed a Collection of Forms, which, consists in adding each answer (with a label of the question) to the Report in the Controller and then rendering the ReportType. The problem about this is that each of my questions have different type (Choice, Text, Integer...) and I don't know how to customize them individually.
And I also tried to do this in my ReportType but it only shows the last question added.
I appreciate any help, thanks!
I managed to get it working. In the end I followed the instructions on How to Embed a Collection of Forms.
To make it work I added the fields question and questionType to my Answer entity. And on the AnswerType.php I check everytime what type of question it is, and then I create the type of the answer based on that. It looks like this:
$builder->addEventListener(FormEvents::POST_SET_DATA, function ($event) {
$builder = $event->getForm();
$answer = $event->getData();
$questionType = $answer->getQuestionType();
$question = $answer->getQuestion();
if ($questionType == 1){
$builder->add('answer', ChoiceType::class, array(
'label' => $question,
'multiple' => false, 'expanded' => false,
'choices' => array('OK' => 'OK', 'NG' => 'NG', 'NP' => 'NP',),
));
}
else if($questionType == 2){
$builder->add('answer', ChoiceType::class, array(
'label' => $question,
'multiple' => false, 'expanded' => false,
'choices' => array('SI' => 'SI', 'NO' => 'NO',),
));
}
else ...
}
You can use the Entity–Attribute–Value model to solve this problem. I would suggest using sidus/eav-model-bundle. This would be a great deal of work but you would learn alot. Using EAV model, you can have N number of questions and answers.
I can not think of solving this using simple form types.

Entities passed to the choice field must be managed - different on app.php and on app_dev.php

Welcome!
I've added a field to a form
->add('languageLevel', ChoiceType::class, [
'choices' => [
Meme::LEVEL_BEGINNER => Meme::LEVEL_BEGINNER,
Meme::LEVEL_INTERMEDIATE => Meme::LEVEL_INTERMEDIATE,
Meme::LEVEL_ADVANCED => Meme::LEVEL_ADVANCED,
Meme::LEVEL_EXPERT => Meme::LEVEL_EXPERT
]
])
This form saves entity 'Meme' with the same property
/**
* #var string
* #ORM\Column(name="language_level", type="string", length=20)
*/
private $languageLevel;
The prod.log gives me this:
[2017-12-17 23:41:18] request.INFO: Matched route "meme_add". {"route":"meme_add","route_parameters":{"_controller":"AppBundle\\Controller\\MemeController::addAction","_route":"meme_add"},"request_uri":"http://keenweasel.com/meme/add","method":"GET"} []
[2017-12-17 23:41:18] security.DEBUG: Read existing security token from the session. {"key":"_security_main"} []
[2017-12-17 23:41:18] security.DEBUG: User was reloaded from a user provider. {"username":"acid","provider":"FOS\\UserBundle\\Security\\UserProvider"} []
[2017-12-17 23:41:18] php.WARNING: Warning: spl_object_hash() expects parameter 1 to be object, integer given {"exception":"[object] (ErrorException(code: 0): Warning: spl_object_hash() expects parameter 1 to be object, integer given at /var/www/keen/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1239)"} []
[2017-12-17 23:41:18] php.WARNING: Warning: spl_object_hash() expects parameter 1 to be object, integer given {"exception":"[object] (ErrorException(code: 0): Warning: spl_object_hash() expects parameter 1 to be object, integer given at /var/www/keen/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1592)"} []
[2017-12-17 23:41:18] request.CRITICAL: Uncaught PHP Exception Symfony\Component\Form\Exception\RuntimeException: "Entities passed to the choice field must be managed. Maybe persist them in the entity manager?" at /var/www/keen/vendor/symfony/symfony/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php line 98 {"exception":"[object] (Symfony\\Component\\Form\\Exception\\RuntimeException(code: 0): Entities passed to the choice field must be managed. Maybe persist them in the entity manager? at /var/www/keen/vendor/symfony/symfony/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php:98)"} []
[2017-12-17 23:41:18] security.DEBUG: Stored the security token in the session. {"key":"_security_main"} []
in dev.log on my dev machine there is nothing like that, and it works fine. On prod I get error 500. Any help please? I tried removing vendor and installing it one more time on both dev and prod servers. Clearing cache multiple time with --env=prod.
EDIT
Ok, got the same problem on dev server (just switched from app_dev.php to app.php in apache config and added app/autoload.php)
/**
* #Route("/meme/add", name="meme_add")
* #param Request $request
* #return \Symfony\Component\HttpFoundation\Response
* #internal param Request $request
*/
public function addAction(Request $request)
{
$user = $this->getUser();
if (!$user || !$user->hasRole('ROLE_MEME_ADD_AWAITING')) {
$this->addFlash('notice', 'You need to login');
return $this->redirectToRoute('fos_user_security_login');
}
if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw $this->createAccessDeniedException();
}
$meme = new Meme();
$form = $this->createForm(MemeAddType::class, $meme);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid() ) {
if ($user->hasRole('ROLE_MEME_ADD_AWAITING')) {
$meme->setPublishingStatus('awaiting-publishing');
} else if ($this->getUser()->hasRole('ROLE_MEME_ADD_MAIN')) {
$meme->setPublishingStatus('published');
}
$em = $this->getDoctrine()->getManager();
$em->persist($meme);
$em->flush();
}
return $this->render(
'#App/memes/add.html.twig',
[
'memeAddForm' => $form->createView()
]
);
}
The form:
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Language;
use AppBundle\Entity\Meme;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
class MemeAddType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('textOne', TextareaType::class, [
'label' => 'A phrase or a word',
'attr' => ['style' => 'width:320px; height: 110px']
])
->add('textOneLang', EntityType::class , [
'class' => Language::class,
'choice_label' => 'language',
'expanded' => false,
'multiple' => false,
'label' => 'Language of the phrase of the word',
'data' => 2
])
->add('textTwo', TextareaType::class, [
'label' => 'Translation, might be with an example',
'attr' => ['style' => 'width:320px; height: 110px']
])
->add('textTwoLang', EntityType::class , [
'class' => Language::class,
'choice_label' => 'language',
'expanded' => false,
'multiple' => false,
'label' => 'Language of the translation of the phrase of the word',
'data' => 2
])
->add('languageLevel', ChoiceType::class, [
'choices' => [
Meme::LEVEL_BEGINNER => Meme::LEVEL_BEGINNER,
Meme::LEVEL_INTERMEDIATE => Meme::LEVEL_INTERMEDIATE,
Meme::LEVEL_ADVANCED => Meme::LEVEL_ADVANCED,
Meme::LEVEL_EXPERT => Meme::LEVEL_EXPERT
]
])
->add('imageFile', VichImageType::class)
->add(
'save',
SubmitType::class,
[
'attr' => ['class' => 'save']
]
)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Meme::class
]);
}
public function getBlockPrefix()
{
return 'app_bundle_meme_add';
}
public function getName() {
return 'app_bundle_meme_add';
}
}
Try this in your formType
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Language;
use AppBundle\Entity\Meme;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
class MemeAddType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('textOne', TextareaType::class, [
'label' => 'A phrase or a word',
'attr' => ['style' => 'width:320px; height: 110px']
])
->add('textOneLang', EntityType::class , [
'class' => Language::class,
'choice_label' => 'language',
'expanded' => false,
'multiple' => false,
'label' => 'Language of the phrase of the word',
'data' => 2
])
->add('textTwo', TextareaType::class, [
'label' => 'Translation, might be with an example',
'attr' => ['style' => 'width:320px; height: 110px']
])
->add('textTwoLang', EntityType::class , [
'class' => Language::class,
'choice_label' => 'language',
'expanded' => false,
'multiple' => false,
'label' => 'Language of the translation of the phrase of the word',
'data' => 2
])
->add('languageLevel', ChoiceType::class, [
'choices' => [
Meme::LEVEL_BEGINNER => Meme::LEVEL_BEGINNER,
Meme::LEVEL_INTERMEDIATE => Meme::LEVEL_INTERMEDIATE,
Meme::LEVEL_ADVANCED => Meme::LEVEL_ADVANCED,
Meme::LEVEL_EXPERT => Meme::LEVEL_EXPERT
]
])
->add('imageFile', VichImageType::class)
->add(
'save',
SubmitType::class,
[
'attr' => ['class' => 'save']
]
)
;
$builder->get('languageLevel')->resetViewTransformers();
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Meme::class
]);
}
public function getBlockPrefix()
{
return 'app_bundle_meme_add';
}
}
Actually, what was going on was that I had
'data' => 2
in the textOneLang form element, so it was failing when it was given to spl_object_hash() function which only accepts objects, not integers. Data transformer was not used for that. But this 'data' parameter wasn't supposed to be there in the first place, don't know how it ended up there. Probably, when trying to debug it. Lesson learned, have all the options figured out and also, just dump what you have as parameter in spl_object_hash().
The difference between app_dev.php and app.php didn't have anything to do with it. It was probably cached somehow in app_dev.php and worked on some older version.

remove entity from entity form field if custom condition is matched

I have an unsubscribe form, where the first field is a entity field, fetching my UnsubscribeType entities :
$builder
->add('type', 'entity', array(
'class' => 'Evo\SubscriptionBundle\Entity\UnsubscribeType',
'property' => 'name',
'label' => 'Choose process type',
'empty_value' => 'Choose an option',
'empty_data' => null,
'label_attr' => array(
'class' => 'control-label',
),
'attr' => array(
'class' => 'form-control',
),
))
this collection contains 2 entities, #1 and #2. I would like to remove entity #2 from the select field if a custom test about the authed user fails.
Let's say I'd to test $this->getUser()->getCustomField(). If it is false, then i would like to remove entity #2 from the select field containing my UnsubscribeType entities.
Any idea how to do that ?
Perhaps you could pass to the form builder the result of the test from you controller and then, either use 'property' => 'name' or 'choices => // fetch entity #1 here.
So, something like this
public function buildForm(FormBuilderInterface $builder, array $options)
{
if($options['customField'] === TRUE)
{
$builder
->add('type', 'entity', array(
'class' => 'Evo\SubscriptionBundle\Entity\UnsubscribeType',
'property' => 'name',
// ..
;
}
else
{
$builder
->add('type', 'entity', array(
'class' => 'Evo\SubscriptionBundle\Entity\UnsubscribeType',
'choices' => $options['customField'],
// ..
;
}
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
// ..
$resolver->setRequired(array(
'customField'
));
}
And in your controller:
$form = $this->get('form.factory')->create(new EntryType(), $entry, array(
'customField' => $this->getUser()->getCustomField()
));
with getCustomField() returning either true or the entity (or collection of entities) that you want populating your select field. My solution would involve changing the getCustomField method though, don't know if that would suit.

Symfony form creates new object and create first one-to-many object

I have an entity for support tickets: SupportTicket(). I also have an entry for replies to each ticket: SupportEntry(). I setup a one-to-many relationship between SupportTicket() and SupportEntry().
Now what I'm trying to do is build my form so that it creates the initial SupportTicket and then inserts the first SupportEntry, all in the same form. I've been messing around with my code for a while, only half-understanding what I'm doing, but this is where I'm at right now:
// My controller, creating the form
$supportTicket = new SupportTicket();
$form = $this->createFormBuilder($supportTicket)
->add('subject', 'text', array(
'label' => 'Subject'
))
->add('jobNumber', 'text', array(
'label' => 'Job Number'
))
->add('supportGroup', 'entity', array(
'label' => 'Group',
'class' => 'ShawmutClientBundle:SupportGroup',
'property' => 'name',
'multiple' => true,
'expanded' => true
))
// ->add('supportEntries', new SupportEntryType())
->add('supportEntries', new SupportEntryType())
->add('Save', 'submit')
->getForm();
My attempt at the custom form type
<?php
namespace Shawmut\ClientBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SupportEntryType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('comment', 'textarea');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Shawmut\ClientBundle\Entity\SupportEntry',
));
}
public function getName()
{
return 'SupportEntryType';
}
}
The form does have the comment box that I've pulled in from the form type, but when I try to submit the form, I get this error:
Neither the property "supportEntries" nor one of the methods "setSupportEntries()", "_set()" or "_call()" exist and have public access in class "Me\MyBundle\Entity\SupportTicket".
And yeah, that makes sense. It should be the addSupportEntries() method which is there. So how do I tell the form builder to use addSupportEntries instead of setSupportEntries?
Thanks in advance
Give the collection form type a go.
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'cascade_validation' => true,
)
)
If you are using the collection form type, and the textarea is not showing, add:
'allow_add' => true
to the properties array().
The code would look something like this:
->add(
'supportEntries',
'collection',
array(
'type' => new SupportEntryType(),
'label' => 'Support Entries',
'error_bubbling' => true,
'allow_add' => true
'cascade_validation' => true,
)
)
To show the widget, assuming you are using twig:
{{ form_widget(form.supportEntries.vars.prototype.comment) }}
For saving the support entry, depending on how you built your entities, you might need to make some extra modifications.
The documentation should help you get it right:
How to Embed a Collection of Forms

Symfony2 form populating entitiy based on other entity

I'm using Symfony2 form component to create and modify forms. I'm currently loading all model entities as choices, but I need to get only the Models related to the selected (posted) value from manufacturer.
How can I achieve this?
class VehicleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false
));
$builder->add('model', 'entity', array(
'label' => 'Model',
'class' => 'BalslevCarBundle:Models',
'property' => 'model',
'empty_value' => 'All',
'required' => false
));
}
public function getName()
{
return 'search';
}
}
First of all, you have to create "dynamically" your choices based onto user selection.
There are two ways, strictly coupled with your application behaviour, that leads (in both cases) at the same "core" code; the difference is only the way you call it.
So, let's explain the two possibilities:
Select manufacturer "statically" and don't change it over the time of request: use data retrieved from your business logic
Select manufacturer "dinamically" and can change them over the time of request: replace your form at every request; new form will be provided by business logic
So, the HEART of your question is: how can I retrieve only associated entities?
Answer
Into your VehicleType.php (where entity, in this case, is VehicleType ?)
private $manufacturerId;
public function __construct($manufacturerId)
{
$this->manufacturerId = $manufacturerId;
}
public function BuildForm(FormBuilderInterface $builder)
{
$manufacturerId = $this->manufacturerId;
$manufacturerQuery = function(EntityRepository $repo) use ($manufacturerId ){
return $repo->createQueryBuilder('m')
->where('m.id = :id')
->setParameter('id',$manufacturerId );};
$builder->add('manufacturer', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $manufacturerQuery;
));
$builder->add('model', 'entity', array(
'label' => 'Brand',
'attr' => array('class' => 'input-block-level'),
'class' => 'BalslevCarBundle:Manufacturers',
'property' => 'short_name',
'empty_value' => 'All',
'required' => false,
'query_builder' => $modelQuery;
));
}
As you can see, in this way we use a custom repository to get only entities that are suitable for our needs. Obviously I am writing my DQL query without knowing your DB structure: it's your task and responsibility to adapt it to your real situation.
Moreover, you have to create another query for model (called $modelQuery in my answer) to retrieve correct models based on selected $id