Ajax Requests in Symfony

Even if Symfony has a clean and beautiful way of handling forms and form submission, most website will at some point require some data to be exchanged silently between the client and server. And this is where an Ajax request comes in handy.

In this tutorial, we are going to build an example of such mechanism. A simple JavaScript code will fire an Ajax request, which will be handled server side by a function in the a controller, which will then send an appropriate response back to the client. The tutorial also includes some optional security verifications that can be implemented server side.

Getting started

Framework : Symfony
Version : 3.4, 3.2, 3.* (?)

Before we get started, we are going to set a minimal context for our application. This is not necessary, but makes the explanations a little easier to follow.

Context : We are going to be handling two types of objects : Products and Templates, sharing, in this case, a bidirectional MayToOne association, as described below.

class Product
{
    ...
    /**
    * Many Products have one (the same) Template
    * @ORM\ManyToOne(targetEntity="Template", inversedBy="products")
    * @ORM\JoinColumn(name="product_id", referencedColumnName="id")
    */
    private $template;
    ...
}

class Template
{
    ...
    /**
    * @ORM\OneToMany(targetEntity="Product", mappedBy="template")
    */
    private $products;
    ...
}

Objective : Fetch and display a list of products corresponding to a template.

Part 1 : Route definition

# Routes definition
# I like specify that the route corresponds to an ajax call
# So my route, as well as my path and function name contain the word ajax

cookie_box_bundle_ajax_get_products_for_template:
    path: /cookie_box/ajax/get/products
    defaults:
        _controller: CookieBoxBundle:Template:ajaxGetProducts

 

Part 2 : JavaScript

Client side, we will be retrieving the id of the template, which will be sent to the server using an Ajax request.

$.ajax({
    type: "POST",
    url: "{{ path('cookie_box_bundle_get_products_for_template') }}",
    data: {template_id: temp_id }
})
.done(function(data){

    if (typeof data.status != "undefined" && data.status != "undefined")
    {
        // At this point we know that the status is defined,
        // so we need to check for its value ("OK" in my case)
        if (data.status == "OK")
        {
            // At this point we know that the server response
            // is what we were expecting,
            // so retrive the response and use if
        
            if (typeof data.message != "undefined" && data.message != "undefined")
            {
                // Do whatever you need with data.message
            }
        }
    }
});

 

Part 3 : PHP (Controller)

Server side, we fetch the request, check the integrity of the received data, handle it, and send a proper response back to the client.

<?php 
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;

public function ajaxGetProductsAction(Request $request)
{
    $entityManager = $this->getDoctrine()->getManager();

    // This is optional.
    // Only include it if the function is reserved for ajax calls only.
    if (!$request->isXmlHttpRequest()) {
        return new JsonResponse(array(
            'status' => 'Error',
            'message' => 'Error'),
        400);
    }

    if(isset($request->request))
    {
    
        // Get data from ajax
        $template_id = $request->request->get('template_id');
        
        // Check if the data is integer
        // I can do this because I know that I am sending the ID of a template.
        // However, similar tests should be performed with other types of data
        // to ensure that the data has not been tampered with
        // Never trust client side data)
        $template_id = intval($template_id);
        
        if ($template_id == 0)
        {
            // You can customize error messages
            // However keep in mind that this data is visible client-side
            // You shouldn't give out clues to what went wrong to potential attackers
            return new JsonResponse(array(
                'status' => 'Error',
                'message' => 'Error'),
            400);
        }
        
        // Check that the template object really exists and fetch it
        $templateRepository = $entityManager->getRepository('PinkGeekBundle:Template');
        $template = $templateRepository->findOneBy(array(
            'id' => $template_id
        ));
        
        if ($template === null)
        {
            // Looks like something went wrong
            return new JsonResponse(array(
                'status' => 'Error',
                'message' => 'Error'),
            400);
        }
        
        // All the test were perfomed, time to handle the data and perform the request
        // Which in our case means retriving the products for
        $products = $template->getProducts();
        
        // Transform data to whatever form is needed for the client side
        // In my case, I need an array with names and ids
        
        $products_array = array();
        foreach ($products as $product)
        {
            array_push($products_array, array(
                'name' => $product->getName(),
                'id' => $product->getId()
            ));
        }
        
        // Send all this back to client
        return new JsonResponse(array(
            'status' => 'OK',
            'message' => $products_array),
        200);
    }

    // If we reach this point, it means that something went wrong
    return new JsonResponse(array(
        'status' => 'Error',
        'message' => 'Error'),
    400);
}

 

That’s it, have a cookie

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.