Handling Ajax post requests with Symfony

Handling Ajax post requests with Symfony
Categories
Technologies
Author

Miro Lehtonen

Project Manager & Lead Developer
Date

Handling PHP forms and accessing the data that follows user input is the bread and butter of learning PHP. Symfony adds a Model-View-Controller (MVC) architecture with reusable components, a well-organized structure, and best practices to the development process, making it a typical environment for handling user input. Ajax, the technology that enables dynamic content on web pages, allows users to complete actions on the page without page transitions or reloads by processing data in the background, sending requests to the server and updating the page view according to the responses. Also known as asynchronous Javascript, Ajax functionality is often used instead of PHP forms to send user input data to the server.

Challenge: Adding items to shopping cart

Let’s assume we want to add items to the shopping cart. Resulting from the user input, we have a set of items with an id and quantity. The (id, quantity) pairs are the data that we send asynchronously to the server. Our choice of technology is a commonly used Javascript library called axios which comes with an interface for sending post requests to a given URL with the data in the request body. The reason behind that choice is that it’s easy to integrate into a VueJS or ReactJS environment.

The function call

axios.post(url, payload)
sends an asynchronous HTTP POST request to the route of the url variable with the data stored in the payload variable.

The Symfony controller handling the route is called addItems.

use Symfony\Component\HttpFoundation\Request;
public function addItems (Request $request) {

    // process data here, for example:
    $quantity = $request->request->get('quantity');

}

Normally, when processing PHP form data, the form field values are accessible through the $_POST variable which is mapped to $request->request. When we post an HTML form, the data is accessible as expected, but now that we make an Ajax request with axios, $request->request is empty. Did our data disappear? Why? Let’s compare HTML form submissions and axios ajax requests.

Cause: content type differences

Posting an HTML form sends the payload as a url-encoded string by default. One of the request headers indicates the content type.

Content-Type: application/x-www-form-urlencoded

Axios.post serializes the payload into a JSON string by default which also shows in the content type header.

Content-Type: application/json;

Because the JSON format allows for the storage of data with an arbitrary depth, it cannot be safely mapped to the Parameter bag PHP structure of $request->request. So we have to adjust our code to work with axios.post requests.

Code that solves the problem

Solution #1

Send the payload as a JSON string and modify the PHP code. Accessing the raw request body in the controller happens through getContent(). This raw JSON data has to be decoded into a PHP object through which the field values are accessible as properties.

$postData = json_decode($request->getContent());
$quantity = $postData->quantity;

Solution #2

Send the payload as a url-encoded string by modifying the Javascript code. We create a URLSearchParams object for the payload because axios does not automatically convert it into a JSON string. In a browser environment, we may also use the FormData interface instead of URLSearchParams.

const params = new URLSearchParams();
params.append('id',1234567890);
params.append('quantity',4);

Then post the data. For example:

const submitData = () => {
  try {
    return axios.post(url,params);
  } catch (error) {
    console.log(error);
  }
}
response = submitData()
  .then(({data}) => {
    console.log(data);
  });

Then the post data is normally accessible in the controller, for example :

$quantity = $request->request->get('quantity');

Other solutions

Whenever the first idea feels too hard or time-consuming to implement, we are inclined to look for an alternative solution. So how about sending an HTTP GET request instead? Packing the few parameters in the URL should be ok, right? It’s easy, it works, so why not use axios.get? Well here’s why not.

Something being easy or quick to implement is not relevant when we choose between HTTP post and get requests. Instead, we should ask if we can repeat the action without consequences, a feature also known as idempotence. For example, adding items to the shopping cart should always have consequences which show as data updates on the server. Each repeat of the action should add more items to the cart. So what if we implement this with a “get” request? The first time probably works just fine but the second time – we may get an “items successfully added” response message cached by the browser with no request ever reaching the server. Besides being a clearly unwanted consequence, it also explains why a get request cannot replace a post request. If we really need an alternative solution, we can use a different Javascript library as an HTTP client. For example, jQuery comes with full support for Ajax.

Miro Lehtonen · Project Manager & Lead Developer

Academic scholar with a research focus on information retrieval and web technology, educational and team leader experience at several institutions in Finland, Thailand, and Australia. Currently also an adjunct lecturer at Mae Fah Luang University, Thailand. Miro has been applying his academic expertise in a number of different projects as the IT Architect of web portals.

Add comment

Related blog articles