Categories
Web Development

How to make AJAX requests to Symfony 5+ controllers

I couldn’t find anything in the documentation about this and there is little on the internet about it too.  There is probably a Bundle somewhere for this or some Symfony way, but I didn’t find anything.

I did find this SymfonyCast about submitting a whole form. If you need to test your Controller route read this. If you want to know more read this in the docs about Browserkit.

But what if you need to just send a simple AJAX request to a controller route? Well that is what this is about. This is very basic, there are settings you can add to the request for mime type for example and more.

How to send a simple AJAX request?

First you need the javascript to make a request to your controller endpoint. To do this you will need access to your routes, in your javascript. Read my article How to get URL Routes in your Javascript in Symfony 5+   to find out how.

Once you have your routing setup and you understand how that works you need to build your AJAX request. I use a custom class I built called EzAjax.js which is a wrapper around the jquery ajax function shown below.

The AJAX solution
import {Utils} from "./Utils";

class EZAjax {
    /**
     *
     * @param errorCallback
     * @param successCallback
     * @param requestUrl
     * @param data
     * @param ajaxOptions
     */
    constructor(errorCallback, successCallback, requestUrl, data, ajaxOptions = {}) {

        this.ajaxObj = null;
        this.ajaxOptionsObj = ajaxOptions;
        this.attempts = 0;
        this.delay = 300;
        this.errorCallback = errorCallback;
        this.successCallback = successCallback;
        //add the settings to the ajaxOptions
        this.ajaxOptionsObj.url = requestUrl;
        this.ajaxOptionsObj.data = data;
        this.ajaxOptionsObj.error = this.ajaxError.bind(this);
        this.ajaxOptionsObj.success = this.ajaxSuccess.bind(this);

    }

    ajaxError(jqXHR, errorString, errorThrown) {
        console.log('error string ' + errorString + ' error thrown ' + errorThrown);
        if (this.attempts <= 3) {
            console.log('attempts is ' + this.attempts + ' delay ' + this.delay);
            setTimeout(() => {
                this.performRequest();
            }, this.delay *= 2);
        } else {
            this.errorCallback(jqXHR, errorString, errorThrown);
        }
    }

    ajaxSuccess(response) {
        this.successCallback(response);
    }

    performRequest() {

        this.attempts++;
        console.log('performing request ' + this.attempts);
        //prevent multiple requests
        if (this.ajaxObj) {
            this.ajaxObj.abort();
        }
        console.log('the ajax options ' + JSON.stringify(this.ajaxOptionsObj));
        this.ajaxObj = $.ajax(this.ajaxOptionsObj);
    }

}

export {EZAjax};

This makes it easier to work with. I just create a simple fail and success function and pass the names in like this.

let ezAjax = new EZAjax(emailExistsError, emailExistsSuccess, checkEmailUrl, data);
ezAjax.performRequest();
Testing inside Javascript

The below code goes inside a class you place inside app\assets\javascript\ directory I called mine registration.js  Below is a snippet of the code for checking if the email exists.


//code for comparing email fields before submit and make sure the email doesn't exist
$(emailMatchField).focusout(function (event) {
    let email = $(emailField).val();
    let emailConfirm = $(emailMatchField).val();

    if (email !== emailConfirm) {

        $('#noEmailMatch').remove();
        $(emailMatchField).css('background-color', redBgColor);
        let matchHtml = '<small id=' + emailMatchErrorId + '" class="red-text" >Email fields must match</small>';
        $(emailMatchField).after(matchHtml);

    }
  if (email === emailConfirm) {
        let data = {"email" : email};
        // if the email fields match make sure the email does not exist in the system
        let checkEmailUrl = Routing.generate( 'email_exists');
        let ezAjax = new EZAjax(emailExistsError, emailExistsSuccess, checkEmailUrl, data);
        console.log('the route is ' + checkEmailUrl);
        ezAjax.performRequest();
        $('#noEmailMatch').remove();
        $(emailMatchField).css('background-color', whiteBgColor);
    }
});

The first if statement checks if the fields match if not it shows an error. The second if statement checks if the email exists only if both fields match first.

The emailExistsError function will open a dialog to inform the user the email they entered exists and give them a login link instead. emailExistsSuccess does nothing I could just put function() in it’s place if I wanted.

Notice this line though

let data = {"email" : email};

That is the data we send via the EzAJAX class. That is the value we need in the controller.

The Symfony controller

Now we need to be able to work with the values sent in the request.

public function checkEmailExists(Request $request)
{
    $json = array();

    if ($request->isXmlHttpRequest()) {
        //check if the email exists
        $email = $request->query->get('email');
        $email = DataSanitizer::sanitizeEmail($email);
        $exists = $this->getDoctrine()->getRepository(User::class)->checkEmailExists($email);
        if($exists){
            $loginUrl = $this->generateUrl('app_login');
            $linkAttr = array(
                AnchorTagAttributes::HREF_ATTRIBUTE => $loginUrl,
                GlobalHtmlAttributes::CLASS_ATTRIBUTE => 'h5'
            );
            $loginLink = AnchorTag::getAnchorTag('login', $linkAttr);
            $json['error'] = "A user with this email exists. Please $loginLink instead. Or use another email.";
        }
    } else {
        $json['error'] = $this->illegalRequest;
    }
    return new JsonResponse($json);
}

To get the value sent via AJAX in the controller I use:

$email = $request->query->get(’email’);

Which matches the value I sent in the data JSON object to this controller.

As you can see in the javascript I am passing a value for email and in the controller I am fetching that value. I didn’t add any mime type info to the request, so the default for jquery ajax is 'application/x-www-form-urlencoded; charset=UTF-8'. To change the MIME type set the contentType on the ajax method.

Also note that this way of working with an Ajax request in the controller is not secured. Anyone from any IP can send a request to this, which can flood the server or allow them access to things they shouldn’t be allowed to access.

Soon I will dig into how to best secure the Symfony Contoller routes and write an article about that.

Leave a Reply

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