I need to be able to do an operation on form data before it gets persisted to the database. The problem is, the operation can be risky and I need the user's consent each time.
I'd like to do this through a confirmation form (with a little message explaining what's going on), not with a Javascript confirm window.
How can I achieve this functionality?
Here is an example of a controller action method that handles the form:
<?php
public function indexAction(Request $request)
{
...
$form = $this->createFormBuilder($myEntity)
->add('someField', 'integer', array('required' => true))
// Lots and lots of fields
->getForm();
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
// CUSTOM VALIDATION HERE
// If invalid, must display a new confirmation form to ask user
// if it's alright to do a somewhat risky operation that would
// validate the form data.
// Else, persist the data.
$db = $this->getDoctrine()->getManager();
$db->persist($myEntity);
$db->flush();
return $this->redirect($this->generateUrl('my_path_to_success_page'));
}
}
return $this->render('MyBundle:Preferences:index.html.twig', array(
'form' => $form->createView(),
'errors' => $form->getErrors(),
));
}
You might want to look into CraueFormFlowBundle, which allows you to create multi-step forms.
Related
For my Poll Application i created a FormType called CampaignType which holds a CollectionType named blocks which in turn holds a CollectionType named lines, which holds a CollectionType named fields, which holds a CollectionType named pollResults.
In my next code example you can see my code that renders the View to fill a campaign(poll).
public function fillAction(Request $request, $id)
{
$campaign = $this->getDoctrine()->getRepository(Campaign::class)->find($id);
$entityManager = $this->getDoctrine()->getManager();
foreach ($campaign->getBlocks() AS $block){
foreach ($block->getLines() AS $line){
foreach ($line->getFields() AS $field){
$pollResult = new PollResult();
$pollResult->setCampaign($campaign);
$pollResult->setField($field);
$pollResult->setUser($this->getUser());
$entityManager->persist($pollResult);
$field->getPollResults()->add($pollResult);
}
}
}
$form = $this->createForm(CampaignType::class, $campaign);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
var_dump("true");
//$entityManager->persist($campaign);
$entityManager->flush();
return $this->redirectToRoute("grappt_poll_campaignShow", ['id' => $id]);
}
return $this->render('GrapptPollBundle:Campaigns:fill.html.twig', [
'campaign' => $campaign,
'form' => $form->createView()
]);
}
The only thing that must be persisted in the database are the PollResults.
Every PollResult has an entry for the campaign_id and the field_id it belongs to, the user_id who filled out the campaign and the value the user chose (and of course its own id, which gets generated automatically).
My Problem is that i don't know how to do that.
Where do i have to call $entityManager->persist($pollResult);.
Right now i put it directly under the initialization-stuff.
Do i have to put it into the if($form->isSubmitted() && $form->isValid())-query and loop through every pollResult?
Do i have to call $entityManager->persist($campaign); although nothing changes there?
Furthermore i wonder if i have to add something for the value-entry of each PollResult?
Thanks in advance for every answer
lxg
What will $form->isValid() return ?
It will depend on the validation constraints of you master form. If your validation constraints are in the annotations of your entity, in your master entity you should have the #Assert\Valid() annotation which will be sure that the nested form is valid :
class Campaign
{
/**
* #ORM\OneToMany(…)
* #Assert\Valid() // <- this line here
*/
private $blocks;
...
If you prefer to put your validation constraints in your CampaignType, you can put it in the options :
public function buildForm (FormBuilderInterface $builder, array $options)
{
$builder
->add('blocks', CollectionType::class,[
'entry_type' => BlockType::class,
'constraints' => array(new Valid()) // <- this line here
...
So, where should you put the persist()?
The best is to have Symfony's form validation (->isValid()) before any persistance, for security and data sanity (don't persist before ensuring csrf protection for instance). If you may add a lot of data (like persisting thousands of entities after one form submission), you can look into Doctrine's batch processing and bulk inserts : https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/batch-processing.html
Should you also persist the Campaign object ?
It depends on the cascade persistence rules you have in your entity.
You can find all the rules to fine-tune the cascade here : https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/working-with-associations.html#transitive-persistence-cascade-operations
I have a form that I use both for registration and edition of the user informations. This form contains a profile picture property on which I put #Assert\Image.
I succeed in creating a new user through my registration form but when I try to edit the user informations (with a PATCH method, just to update what need to be updated) I encounter an error with a 'File could not be found' message.
I suppose it's because the path stored in the database is a string and my #Assert\Image want an image.
I'm not sure about how I should manage this kind of update.
When I dd() the $user right after the submission, I see that the profilePicture property still contains the path saved in the database.
Here is my function regarding the form handling:
public function myProfile(Request $request)
{
$user = $this->getUser();
$form = $this->createForm(UserFormType::class, $user, ['method' => 'PATCH']);
if ($request->isMethod('PATCH')){
$form->submit($request->request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
//...
}
}
//if no request just display the page
return $this->render('connected/myProfile.html.twig', [
'user' => $user,
'userProfileForm' => $form->createView()
]);
}
The Validator will check if your object contains a image and that seems not the case when you’re updating your object.
A workaround is to use group validation you define a specific group to the property that have the assert Image and in the method getGroupSequence you return the group if you’re in creation (id == null) or if the property is setted.
I used to use edit and update methods in my controller to submit and handle a PUT form submission. It works fine and the code looks like this,
public function edit(Category $category): Response
{
$form = $this->createForm(CategoryType::class, $category, [
'action' => $this->generateUrl('category_update', [
'id' => $category->getId(),
]),
'method' => 'PUT',
]);
return $this->render('category/edit.html.twig', [
'category_form' => $form->createView(),
]);
}
public function update(Category $category, Request $request): Response
{
$form = $this->createForm(CategoryType::class, $category, ['method' => 'PUT']);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->flush();
return new Response('', Response::HTTP_NO_CONTENT);
}
return new Response('', Response::HTTP_BAD_REQUEST);
}
As PUT is not supported by HTML forms, the edit request uses POST with a '_method' parameter as 'PUT' instead of a real PUT request.
Now I want to remove the edit method and send a real PUT request from the frontend. When I used Postman to test this, I found the update method cannot handle a real PUT request.
When I use Postman to send POST + '_method'='PUT' requests, it works fine, but when I send PUT requests, it shows BAD_REQUEST, which is the last line in my code. isSubmitted() returns false.
I know I don't need to use Forms here, but it's been used in the store method. Is it possible to use it to handle a real PUT request? What should I change in my update method?
Seems like you're missing $entityManager->merge($category); in the update() method. Try adding it above $entityManager->flush(); and let us know if it works.
You need to write _method instead of method
$form = $this->createForm(CategoryType::class, $category, ['_method' => 'PUT']);
Also you need to persist object, before flushing
I want to be able to submit a form that modifies multiple entities. Let's say I've got a BulkeditFormType to change the 'active' (Boolean) or 'organisation' (EntityType with App\Entity\Organisation) fields on multiple users at once.
This is my current solution (semi-pseudo-code):
public function bulkedit(Request $request)
{
$form = $this->createForm(BulkeditFormType::class, null, [
//..options
]);
if ($request->isXmlHttpRequest()) {
// fields '_token' and empty values are unset, not shown in this example
$formData = $request->request->get($form->getName());
$entityManager = $this->getDoctrine()->getManager();
$repo = $entityManager->getRepository(App\Entity\User::class);
$entities = $repo->findBy([
'id' => [1,2,3,4,5]
]);
foreach ($entities as $entity) {
$form = $this->createForm(BulkeditFormType::class, $entity, [
//..options
]);
$clearMissing = false;
$form->submit($formData, $clearMissing);
$entityManager->persist($entity);
}
$entityManager->flush();
}
return $this->render('#User/User/bulkedit.html.twig', [
'form' => $form->createView()
]);
}
Note that I've tried to include only relevant parts of my code, so please consider this as pseudo-code, just to get an idea of my current implementation.
While this solution works, it creates a Form object for every entity. Editing 100 users will lead to a large amount of memory usage and many useless database queries.
How can I modify my code in such a way that only one form will be generated which can be re-used by all entities? I've tried to use $form->setData(), but I feel like there must be a better way. A CollectionType will create multiple subforms instead of multiple forms, so in this case it doesn't make a big difference.
I'm using this bundle in an application. The controller is the typical that shows a search form, take the response and process it (an example):
public function indexAction()
{
$request = $this->getRequest();
$example = new Example();
$form = $this->createForm(new ExampleFindType(), $example, array(
'action' => $this->generateUrl('example_find'),
'method' => 'POST',
));
$form->handleRequest($request);
if ($form->isValid())
{
$em = $this->getDoctrine()->getManager();
$examples = $em->getRepository('ApplicationExampleBundle:Example')
->find_by_fields($example);
$paginator = $this->get('knp_paginator');
$pagination = $paginator->paginate(
$examples,
$this->get('request')->query->get('p', 1),
20
);
return $this->render('ApplicationExampleBundle:Default:searchResults.html.twig',
array('pagination' => $pagination));
}
return $this->render('ApplicationExampleBundle:Default:index.html.twig',
array('form' => $form->createView(),
));
}
When I perform the search I see the results list and the paginator correctly. The problem appears when I press the link to the next page. The link id generated well, with the URL ending with "?p=2" but it seems that the form POST data is not resent because it sent me to the search form page ($form->isValid() is false).
If I change the form method from POST to GET and pass the parameters in the URL:
$form = $this->createForm(new ExampleFindType(), $example, array(
'action' => $this->generateUrl('example_find'),
'method' => 'GET',
));
the paginator works perfect.
Am I doing something wrong? Is possible to use a POST form?
I've searched an answer but all the KnpPagintor controller examples I've seen don't generate the query with forms, and this question hasn't helped me.
Thanks.
You shouldn't use POST method to get data.
Otherwise, if you need to use the POST method then you need the data in the session. However it's difficult to build a nice user experience while it just makes more sense to use a GET method.
You can find an extensive documentation about HTTP on MDN.
A GET method should be used when you request data.
A POST method should be used when you save data (like saving a comment into a database) or other data manipulation.
Google uses a GET on their own search page.
https://www.google.com/#q=symfony&start=10
q is what I searched for and start is the paginator value. They probably use an offset instead of a page number to avoid calculating the offset (faster and less expensive).