Strange exception - Serialization of 'Symfony\Component\HttpFoundation\File\File' is not allowed - forms

I have this exception, in a strange behavior that I can't understand. I'll try to explain.
I have a form, where I can add create a report. The use can upload multiple attached pdf and a text comment. The form have 2 submit button, 'save' and 'saveAndClose'.
class ReportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('documentDatas', CollectionType::class, array(
'entry_type' => DocumentType::class,
'entry_options' => ['data_class' => 'AppBundle\Model\DocumentPdfData', 'add_class' => 'upload-pdf'],
'allow_add' => true,
'allow_delete' => true,
'label' => false
))
->add('comment', CKEditorType::class, array(
'config' => array('toolbar' => 'my_toolbar'),
'label' => false,
'required' => false,
'input_sync' => true
))
->add('save', SubmitType::class, array('attr' => array('class' => 'btn btn-success mr-2')))
->add('saveAndClose', SubmitType::class, array('label' => 'saveAndClose', 'attr' => array('class' => 'btn btn-success float-right')))
;
}
}
The Save only persist the form, the saveAndClose obviously close the report, so user can't modify it anymore.
The exception
Serialization of 'Symfony\Component\HttpFoundation\File\File' is not allowed
appear when an user upload an attached and saveAndClose the form. Only in this case, other case works well (also upload attached, push save, return the the form and saveAndClose).
This is my controller:
if ($form->isSubmitted() && $form->isValid()) {
if ($form->getClickedButton() && 'saveAndClose' === $form->getClickedButton()->getName()) {
$this->get(ConfirmReport::class)->confirm($report, $user);
$em->flush();
$report->modify($reportData, $user);
$em->persist($report->getReportParameters());
$em->persist($report);
$em->flush();
$this->addFlash('success', $this->get('translator')->trans('ReportConfirmed'));
return $this->redirectToRoute('practitioner_visit_detail_to_do');
}
if ($form->getClickedButton() && 'save' === $form->getClickedButton()->getName()) {
$report->modify($ReportData, $user);
$em->persist($report->getReportParameters());
$em->persist($report);
$em->flush();
$this->addFlash('success', $this->get('translator')->trans('ReportSaved'));
return $this->redirectToRoute('practitioner_visit_detail_to_do');
}
}
So, the only difference between 2 flux is the confimReport service. Inside this I put a bool parameter of report to true and make some operation on the DB, to remove some read and write permission of the user on this report. Nothing about the file uploaded or something else. So, i can't understand what cause the exception.
N.B.:
If i comment the confirmReport service all work well (dont know why);
I obtain the exception but the entity manager is flushed, so in my db I have saved the operation of the user (also the file uploaded).
So then what else could cause this?

Maybe the second flushing messes up with the code ? Can you try this :
if ($form->isSubmitted() && $form->isValid() && $form->getClickedButton()) {
if ('saveAndClose' === $form->getClickedButton()->getName()) {
$this->get(ConfirmReport::class)->confirm($report, $user);
$msg='ReportConfirmed';
}
if ('save' === $form->getClickedButton()->getName()) {
$msg = 'ReportSaved';
}
$report->modify($ReportData, $user);
$em->persist($report->getReportParameters());
$em->persist($report);
$em->flush();
$this->addFlash('success', $this->get('translator')->trans($msg));
return $this->redirectToRoute('practitioner_visit_detail_to_do');
}
Can you also show the content of the confirm() function ?
Thanks

Related

EventListener adds new fields but Controller doesn't recognize them

I'm trying to add new fields to my form depending on the selected value from a list. My problem comes when clicking on "submit", after the code runs the eventListener method (in which the new field is added), the controller gets the form without the new field! I'm struggling to understand where the problem is.
Here's my form builder:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('DataTypeList', ChoiceType::class, [
'required' => false,
'mapped' => false,
'choices' => [
"String Type" => "DataTypeString",
"Monetary Type" => "DataTypeMonetaryNumber"
],
'attr' => ['style' => 'width: 50vw'],
]);
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if ($data['DataTypeList'] === "DataTypeString") {
$form->add("DataTypeString", TextType::class, [
'required' => true,
'mapped' => false,
'error_bubbling' => true,
'attr' => [
'placeholder' => 'This is an example',
'style' => 'width: 50vw'
]
]);
}
});
}
This is my controller:
$form = $this->createForm('data\AddType', $data, array(
'action' => $this->generateUrl('data_add', [
'dataKey_id' => $data->getDataAccessKey()->getId(),
'returnRoute' => $returnRoute
]),
'method' => 'POST',
));
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//Here I make a dump() on $request that shows that my form only contains 'DataTypeList'
}
Thanks in advance for your help!
This works for me :
if ($form->isSubmitted() && $form->isValid()) {
dump($form->get('DataTypeString')->getData()); //return null;
}
It is return null, because you add your field in PRE_SUBMIT and you don't set data, but there is no excpetion, so DataTypeString field exist.
UPDATE
PRE_SUBMIT event is executed before the submit action in PHP.
This is the process
Create form and build
Set Data to the form (this data is the second parameters of createForm method in your controller)
Build form view and display the form on the browser
You fill your form on your browser and submit data
When you handleRequest in your controller, he looks in $_POST variable and see that you click on the 'submit' button of your form, he execute PRE_SUBMIT event HERE, and he do $form->submit and POST_SUBMIT event
In POST_SUBMIT he validate the form and set errors on the form fields
If your form is not valid you don't enter in if($form->isValid()) and you build the form view. And this time you have the DataTypeString fields in your form, because PRE_SUBMIT add the field
Why do you want to add this field in PRE_SUBMIT ?

Symfony 3 callback validation not being called

I have a form in Symfony 3 where I want the user to upload a file or fill in a comment (or both).
$form = $this->createFormBuilder(
$approvalRequest,
array(
'validation_groups' => array('revocation_proposal')
)
)
->add(
'deletionComment',
TextareaType::class,
array(
'attr' => array(
'cols' => 70,
'rows' => 10
),
'required' => false //otherwise html will force it to be required
)
)
->add('deletionTemplate',
ResearchTemplateType::class,
array('label' => 'Deletion Form',
'required' => false)) //otherwise html will force it to be required
->add(
'cancel',
SubmitType::class,
array(
'label' => 'Cancel'
)
)
->add('revoke', SubmitType::class, array(
'label' => 'Revoke Approval',
'attr' => array('class' => 'btn-danger')
))
->getForm();
$form->handleRequest($request);
I am trying to enforce that at least one of the two (the comment or the file) must be present in order for the form to be valid using callbacks.
In my entity I have:
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context)
{
if (is_null($this->getDeletionComment()) && is_null($this->getDeletionTemplate())) {
$context->buildViolation('You must write a deletion comment or upload a deletion template')
->atPath('deletionTemplate')
->addViolation();
}
}
But it is not being called. Am I missing something?
Thanks

Symfony form EntityType::class - How use it to edit data in form (3.2.13)

A dropdown selection menu in a Form is available by using a EntityType form type. It is usefull if you add data. But when you try to edit data with the same FormType class, your $request data are overwritten by your EntityType.
How use the same FormType class when editing the data? (eg. editAction in controller) How pass Request $request data to FormType fields as "defaults, or selected" for element EntityType::class in FormBuilder? Is there something in $builder->add() method I can use like if(['choice_value'] !=== null ) xx : yy?
How to get something like html select selected, from Request object and pass it to xxxFormType class and bind to right EntityType::class element there.
<select>
<option value="volvo">Volvo</option>
<option value="vw">VW</option>
<option value="audi" selected>Audi</option>
</select>
I've looked at EntityType Field,
How to Dynamically Modify Forms Using Form Events and lots of StackOverflow posts, but can't find proper solution.
Controller:
public function editProdPackageAction(Request $request, ProdPackage $prodPackage)
{
$form = $this->createForm(ProdPackageFormType::class, $prodPackage);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$prodPackage = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($prodPackage);
$em->flush();
$this->addFlash('success', 'MSG#011E');
return $this->redirectToRoute('admin_package_list');
}
return $this->render(':admin/forms/ProdPackage:edit.html.twig',
[
'ppg' => $form->createView(),
]
);
}
Formtype:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'idCatBig',
EntityType::class,
[
'label' => '* category',
'class' => 'AppBundle\Entity\ProdCategoryBig',
'choice_value' => 'id',
'choice_label' => 'shortName',
'multiple' => false,
'expanded' => false,
]
)
->add(
'dateStart',
DateType::class,
[
'label' => '* some date',
'data' => new \DateTime('now'),
'choice_translation_domain' => true,
]
)
->add(
'dateEnd',
DateType::class,
[
'label' => '* till',
'data' => new \DateTime('now'),
]
)
->add(
'packageName',
TextType::class,
[
'label' => '* package',
'attr' => ['placeholder' => 'default Name'],
'data' => 'your default value',
]
)
This is what I do in my form, I set a "pre set data listener" to check whether or not this is an edit or a create
function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add(
'dateStart',
DateType::class,
[
'label' => '* some date'
//I don't set a data field here because it is an edit
'choice_translation_domain' => true,
]
)
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (!$data || null === $data->getId()) {
//if the entity does not have an ID it means that this is a new entity not an edit. because all edits have been in the database and have an id
$builder->add(
'dateStart',
DateType::class,
['label' => '* some date',
'data' => new \DateTime('now'), //this is a create so I override my previous setting and set a default data
'choice_translation_domain' => true,]
)
}
});
}
I mainly use this trick to change form fields from required to non required between edits and creates for password fields for example,and sometimes if there is something exceedingly complicated. To change data, honestly, setting default data in constructor is cleaner as Stephan suggests
Problem
You are overriding an object by using the data option.
https://symfony.com/doc/3.2/reference/forms/types/entity.html#data:
( ! ) The data option always overrides the value taken from the domain data (object) when rendering. This means the object value is also overriden when the form edits an already persisted object, causing it to lose its persisted value when the form is submitted.
Solution 1: use the controller
So the solution is pretty simple: don't use data. Instead, set default values in your addProdPackageAction action (or whatever it is called):
public function editProdPackageAction(Request $request)
{
$prodPackage = new ProdPackage();
$prodPackage->setDateEnd(new Datetime('now'));
//example: use Request or other controller methods to modify your entity
if ($this->getUser()->hasRole('ROLE_ADMIN')) {
$prodPackage->setCreatedByAdmin(true);
}
//your form
}
Solution 2: use your entity constructor
Alternatively, you can use your entity constructor method:
class ProdPackage
{
//your attributes
private $dateEnd;
public function __construct()
{
$this->dateEnd = new Datetime('now');
}
}
Found another interesting solution
in formTypeclass at $builder
->add(
'trBegin',
DateTimeType::class,
[
'label' => 'Tourney Begins',
'required' => true,
'date_widget' => 'single_text',
'time_widget' => 'single_text',
'date_format' => 'dd.MM.yyyy',
]
)
and continuing building form add:
$builder->get('trBegin')->addModelTransformer(
new CallbackTransformer(
function ($value) {
if (!$value) {
return new \DateTime('now + 60 minutes');
}
return $value;
},
function ($value) {
return $value;
}
)
);
it sets default date at moment when form is created. This method is very usefull also for EntityType object, where you can pass id of field in form and get selected your current real choice from database (not all list from the begining) very useful when using EntityField also for editing forms
$builder->get('productCategory')
->addModelTransformer(new CallbackTransformer(
function ($id) {
if (!$id) {
return;
}
return $this->em->getRepository('AppBundle:ProductCategory')->find($id);
},
function($category) {
return $category->getId();
}
));

Not to validate the form in Symfony 2

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.

Symfony2 form refresh same page after submit

I have a form whose content is created from a DB.
in my controller i have:
/**
* #Route("/HR/manage/{projectID}", name="hr_manage")
*/
public function manageHRAction(Request $request, $projectID)
{
//here I get all the data from DB and create the form
if ($form->isValid())
{
//here I do all the relevant changes in the DB
return $this->render('HR/show.html.twig', array('hrlist' => $HRsInMyDomain, 'form' => $form->createView(), 'HRs' => $HRsInThisProject, 'project' => $prj, ));
}
return $this->render('HR/show.html.twig', array('hrlist' => $HRsInMyDomain, 'form' => $form->createView(), 'HRs' => $HRsInThisProject, 'project' => $prj, ));
}
It updates the info on the DB properly, but it does not build again the form with updated data. Instead of the return inside the "isValid()" I simply need a refresh on the current page.
I assume it's possible and easy to accomplish, but I failed to find how to do it :/
EDIT - here comes more relevant code:
/**
* #Route("/HR/manage/{projectID}", name="hr_manage")
*/
public function manageHRAction(Request $request, $projectID)
{
$user = $this->container->get('security.context')->getToken()->getUser(); //get current user
$em = $this->getDoctrine()->getManager(); //connect to DB
$prj = $this->getDoctrine()->getRepository('AppBundle:Project')->findOneById($projectID);
[...]
// here comes some code to generate the list of $HRsInThisProject and the list of roles ($rolesListForForm)
[...]
foreach ($HRsInThisProject as $key => $HR)
{
$form->add('roleOf_'.$key, 'choice', array('choices' => $rolesListForForm, 'required' => true, 'data' => $HR['role'], 'label' => false, ));
$form->add('isActive_'.$key, 'choice', array('choices' => [0 => 'Inactive', 1 => 'Active'] , 'required' => true, 'data' => $HR['is_active'], 'label' => false, ));
}
[...]
// here there is some code to get the $HRsInMyDomainForForm
[...]
$form->add('HRid', 'choice', array('choices' => $HRsInMyDomainForForm,'required' => false, 'placeholder' => 'Choose a resource', 'label' => false, ));
$form->add('role', 'choice', array('choices' => $rolesListForForm,'required' => false, 'placeholder' => 'Choose a role', 'label' => false, ));
$form->add('save', 'submit', array('label' => 'Save'));
$form->handleRequest($request);
if ($form->isValid())
{
{
[...] here there is a huge portion of code that determines if I need to generate a new "event" to be stored, or even multiple events as I can change several form fields at once
// If I needed to create the event I persist it (this is inside a foreach)
$em->persist($newHREvent);
}
$em->flush();
return $this->render('HR/show.html.twig', array('projectID' => $prj->getId(), 'hrlist' => $HRsInMyDomain, 'form' => $form->createView(), 'HRs' => $HRsInThisProject, 'project' => $prj, ));
}
return $this->render('HR/show.html.twig', array('projectID' => $prj->getId(), 'hrlist' => $HRsInMyDomain, 'form' => $form->createView(), 'HRs' => $HRsInThisProject, 'project' => $prj, ));
}
I also include a screenshot of the form:
If a user selects to add a new resouce, I need to persist it to DB (and that is done properly) but then I need to see it in the list of available HRs, without the need for the user to reload the page.
More dynamic way would be:
$request = $this->getRequest();
return $this->redirectToRoute($request->get('_route'), $request->query->all());
or simply
return $this->redirect($request->getUri());
I managed to solve it in a simple (and I hope correct) way.
I simply substituted the "render" inside the isValid() with the following:
return $this->redirect($this->generateUrl('hr_manage', array('projectID' => $prj->getId())));
I works, but does anybody foresee problems with this solution?
You have to link the form to the request.
$entity = new Entity();
$form = $this->createFormBuilder($entity)
->add('field1', 'text')
->add('field2', 'date')
->add('save', 'submit', array('label' => 'Submit'))
->getForm();
$form->handleRequest($request); // <= this links the form to the request.
only after that you test $form->isValid() and pass this form when rendering the template. If you already did this and haven't included in the code above please show more code for better help.
Here is the right way to do it. Event though you have $projectId slug, in Action you can pass whole Object, in this case Project. Symfony will take care for the rest (fetching right Project entity for you.
/**
* #Route("/HR/manage/{projectID}", name="hr_manage")
*/
public function manageHRAction(Request $request, Project $project)
{
$form = $this->createForm(new ProjectType(), $project);
$form->handleRequest($request);
// ... your business logic or what ever
//here I get all the data from DB and create the form
if ($form->isValid() && $form->isSubmitted()) {
$em->persist($project);
// no need to flush since entity already exists
// no need to redirect
}
// here $form->createView() will populate fields with data since you have passed Poroject $project to form
return $this->render('HR/show.html.twig', array('hrlist' => $HRsInMyDomain, 'form' => $form->createView(), 'HRs' => $HRsInThisProject, 'project' => $prj, ));
}
Update
According to your edit, you need to use javascript for client-side dom manipulation. Check this link from Symfony official document embedded forms. Here you'll find an example of what you're trying to accomplish.