Handle PUT requests with Symfony 4 Form - symfony

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

Related

How to add meta fields to media using Wordpress REST API v2 by using Postman?

I want to be able to add meta to a media post type by using WP REST API.
I want to use Postman because, for now, I just want to test how the API is working. The docs seems to be somewhat confusing. I would be grateful if you have any working examples.
Basically, I want to add copyright meta field to the media using this API.
for creating API you need to add route first. you can add route using below code:
function custom_meta_api() {
register_rest_route('wp/v1', '/update_meta/(?P<id>[\d]+)', array(
array(
'methods' => 'POST',
'callback' => 'saveMeta',
),
));
}|
add_action('rest_api_init', 'custom_meta_api');
you can pass your image id in (?P<id>[\d]+)
now in postman write url
http://your-url/wp-json/wp/v1/update_meta/5 with POST request
in body you can write below code
{"data":
{
"copyright":"xyz"
}
}
and to save in postmeta table create function saveMeta(which you have written in callback). Code for the function is below:
function saveMeta(WP_REST_Request $data) {
$bookingID = $data['id'];
$request = $data->get_json_params();
extract($request['data']);
update_post_meta($bookingID, 'copyright', $copyright);
$response = array();
$response["code"] = "success";
$response["message"] = "";
$response["data"] = array();
$response["data"][] = 'meta added';
return $response;
}

Modify multiple entities with one form (bulk edit)

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.

Symfony2: KnpPaginator only show the first page with POST form

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).

Symfony2 confirm form submission

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.

Symfony2 Functional test: Passing form data directly

I am using phpunit to run functional tests but I am having a problem with a few of the forms. The problem is that phpunit is not aware of JS, and I have a form with a dynamically populated select box that needs jQuery.
So I need to pass the form data directly. The 'book' gives the following example:
// Directly submit a form (but using the Crawler is easier!)
$client->request('POST', '/submit', array('name' => 'Fabien'));
When I used this example the controller didn't receive any of the form data. Intially I saw that passing the array key 'name' wasn't correct in my situation as I needed the form name which was 'timesheet' in my code. So I tried something like:
$client->request('POST', '/timesheet/create', array('timesheet[project]' => '100'));
But this still didn't work. In the controller I tried to understand what was happening and what if anything was being received:
$postData = $request->request->get('timesheet');
$project = $postData['project'];
This didn't work and $project remained empty. However if I used the following code I got the value:
$project = $request->request->get('timesheet[project]');
But clearly that's not what I want. Atleast though I can see that there is some POST data. My last attempt was to try the following in the test method:
$this->crawler = $this->client->request('POST', '/timesheet/create/', array('timesheet' => array(project => '100'));
So I am trying to pass a 'timesheet' array as the first element of the request parameter array. But with this I get the error:
Symfony\Component\Form\Exception\UnexpectedTypeException: Expected argument of type "array", "string" given (uncaught exception) at /mnt/hgfs/pmt/src/vendor/symfony/src/Symfony/Component/Form/Form.php line 489
I would be very happy if someone can expand on what's in the 'book' about how I am supposed to get this working.
Form bind in controller:
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$postData = $request->request->get('timesheet');
$project = $postData['project'];
$timesheetmanager = $this->get('wlp_pmt.timesheet_db_access');
$timesheetmanager->editTimesheet($timesheet);
return $this->redirect($this->generateUrl('timesheet_list'));
}
}
If you are wanting to know how to inject arrays of POST data using the test client...
In your test method, do something like
$crawler = $client->request('POST', '/foo', array(
'animal_sounds' => array(
'cow' => 'moo',
'duck' => 'quack'
)
); // This would encode to '/foo?animal_sounds%5Bcow%5D=moo&animal_sounds%5Bduck%5D=quack'
$this->assertTrue( ... );
In the controller, you would access your params like this:
$data = $request->request->get('animal_sounds');
$cowNoise = $data['cow'];
$duckNoise = $data['duck'];
Or you could just use the forms API if the test method was injecting valid form data...
do you have a $request parameter in your action?
that was the reason why my request->get() was empty:
//WRONG
public function projectAction()
{
$request = Request::createFromGlobals();
$project = $request->request->get('timesheet[project]');
//$project will be empty
}
//CORRECT
public function projectAction(Request $request)
{
$project = $request->request->get('timesheet[project]');
//$project is not empty
}
see
How do I create a functional test which includes a POST to a page with parameters?
Try to use $form->bind($clientData) instead of $form->bindRequest($request).

Resources