Want to learn object-oriented programmming? Get my free course

Designing a class: WordPress AJAX handler

Learning object-oriented programming has its fair share of challenges. One of them is the large variety of problems and their solutions. That’s why a lot of solutions revolve around using design patterns.

Sometimes a problem has a clear solution like the WordPress API client. Other times it’s more abstract like how to interact with the plugin API. These abstract problems are a lot harder to define and solve.

That’s why we’ll look at how to design a class to solve one of those abstract problems. I decided to look at AJAX communication. You can use AJAX to do anything. This means that we can only create a generic outline of what an AJAX handler class would look like.

Let’s do a quick overview of AJAX

Before we go any further, let’s go over the idea behind AJAX. AJAX is a way for a rendered web page to communicate with a server without needing to refresh.

The web page sends a request to a server using JavaScript. That request could also contain data for the server to use. The server can then send a response back with information for the web page to use. This can go back and forth forever. The end result is a more dynamic web experience.

For more details, you should take a look at the codex. It does a good job at explaining what AJAX is and how to use it with plugins.

For us, there’s one main idea that’s important. It’s that AJAX is a two-way conversation between a web page and a server. You’ll want to keep that in mind when we design our class.

Breaking down AJAX into tasks

Now that you know a bit more about AJAX. Let’s bring in some single responsibility principle into the picture. We’re going to diverge a bit from the usual formula of finding the job for our class.

That’s because we’re designing around the concept of AJAX and not a specific job. All that we know for sure is that the class needs to work with AJAX. So we’re going to focus on the common tasks it needs to be able to do to work with it.

Receive a request

Your class needs to be able to receive and process an AJAX request. If the request comes with data, you need a way to retrieve it. This is the most basic need of our class. The web page will always send us some information, but it might not always expect a response back.

Send a response

In more advanced use cases, the web page will also expect a response back. That response would contain data as well. That data could be a simple string, but it could also be an encoded JSON object.

Make sure everything is secure

It’s easy to forget about security, but it’s important when working with AJAX. It has its own way of communicating with WordPress which can leave you vulnerable. You have to take the necessary steps to keep WordPress safe.

That means that your class needs to know that if it should process the request it received. It also has to ensure the data passed by the request is safe. That means using sanitization where appropriate.

Let’s start putting it together

Breaking out the tasks like that is useful when dealing with an abstract problem like AJAX. These tasks have specific needs and outcomes. This makes coding them easier.

Now, let’s see how we can weave them together to form our AJAX handler class. We’ll call it AjaxHandler.

Defining our constants

The first thing we’ll take a look at is constants.We want to look at those first because they help us think about unchanging elements of your class. Those elements might also need to be accessible from outside that class. For our class, there are two elements that fit this criteria based on the tasks that we defined earlier.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Action hook used by the AJAX class.
     *
     * @var string
     */
    const ACTION = 'my_plugin';
    
    /**
     * Action argument used by the nonce validating the AJAX request.
     *
     * @var string
     */
    const NONCE = 'my-plugin-ajax';
}

Here’s our AjaxHandler class with our two constants. ACTION is the action used by AjaxHandler. It’s what we expect the AJAX call to send to WordPress.

NONCE is the name of the action that we’ll pass to WordPress nonce system. We’ll use it when we create and validate our nonces. This will make our code a bit more secure.

Both those values should never change and need to be accessible from outside the class. This allows you to use them easily in the JavaScript code in your theme or elsewhere.

Outputting our AJAX data

The safest way to output AJAX data in WordPress is by using the wp_localize_Script. It’s a bit of a hack because the function is for localization. That said, it lets us output our data without using PHP in a JavaScript file.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Register the AJAX handler class with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        $handler = new self();

        add_action('wp_loaded', array($handler, 'register_script'));
    }

    /**
     * Register our AJAX JavaScript.
     */
    public function register_script()
    {
        wp_register_script('wp_ajax', plugins_url('path/to/ajax.js', __FILE__));
        wp_localize_script('wp_ajax', 'wp_ajax_data', $this->get_ajax_data());
        wp_enqueue_script('wp_ajax');
    }

    /**
     * Get the AJAX data that WordPress needs to output.
     *
     * @return array
     */
    private function get_ajax_data()
    {
        return array(
            'action' => self::ACTION,
            'nonce' => wp_create_nonce(AjaxHandler::NONCE)
        );
    }
}

Here are changes that we made to our AjaxHandler class to support it. We added the register static method as a custom constructor. It registers our register_script method with the wp_loaded hook.

The register_script method is in charge of wiring all our JavaScript with WordPress. We have to start by registering our ajax.js with WordPress using wp_register_script. This registers it with the handle wp_ajax.

We use that handle with wp_localize_script. Whenever we enqueue our wp_ajax script, it’ll also output our data. This data comes from our get_ajax_data method. It returns an array with our ACTION constant and the nonce that we’ll use to validate the request. We create it using wp_create_nonce and our NONCE constant.

You can see the result below. It’s a JavaScript snippet from our JavaScript file. It creates a data object using values from the wp_ajax_data object created by WordPress.

// ajax.js

var data = {
    'action': wp_ajax_data.action,
    '_ajax_nonce': wp_ajax_data.nonce
};

jQuery.post(ajaxurl, data);

Handling an AJAX request

Next up, we’ll have to process an AJAX request. We’ll have to register our AJAX action hooks with the plugin API. Let’s look at what this looks like in our AjaxHandler class.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Action hook used by the AJAX class.
     *
     * @var string
     */
    const ACTION = 'my_plugin';

    /**
     * Action argument used by the nonce validating the AJAX request.
     *
     * @var string
     */
    const NONCE = 'my-plugin-ajax';

    /**
     * Register the AJAX handler class with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        $handler = new self();

        add_action('wp_ajax_' . self::ACTION, array($handler, 'handle'));
        add_action('wp_ajax_nopriv_' . self::ACTION, array($handler, 'handle'));
        add_action('wp_loaded', array($handler, 'register_script'));
    }

    /**
     * Handles the AJAX request for my plugin.
     */
    public function handle()
    {
        // Make sure we are getting a valid AJAX request
        check_ajax_referer(self::NONCE);

        // Stand back! I'm about to do... SCIENCE!

        die();
    }

    // ...
}

Lucky for us, we already have our  `register` static method to handle the plugin API. We just needed to update it with it with two new hooks. Both of them prefix our `ACTION` constant. `wp_ajax_` is the standard AJAX hook format, but only runs when `is_user_logged_in` is true.

wp_ajax_nopriv_ is the opposite. It only runs when is_user_logged_in is false. It’s there to make sure that you don’t perform sensitive actions on anyone. If you want to know more about the two hooks, there’s an article about them in the codex.

Both these hooks point to our handle method. It checks if it has a valid AJAX request using check_ajax_referer. This the critical step to secure our AJAX handler.

check_ajax_referer validates the request using the nonce that we passed using wp_localize_script. It uses our same NONCE constant that we used when creating the nonce. If the check fails, WordPress will prevent the rest of our code from running.

What happens after check_ajax_referer isn’t in the scope of this article. That’s because our AjaxHandler class doesn’t solve a specific problem. We’re just focused on solving the generic problems around AJAX that we defined earlier.

Adding extra request data

So far, we’ve kept things to the basics. That said, you’ll often want to send data back through your AJAX request. For example, we could send a text (no HTML) comment to save. But we’ll need the ID of the post to this.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Get the AJAX data that WordPress needs to output.
     *
     * @return array
     */
    private function get_ajax_data()
    {
        return array(
            'action' => self::ACTION,
            'nonce' => wp_create_nonce(AjaxHandler::NONCE),
            'post_id' => get_the_ID()
        );
    }
}

We updated our get_ajax_data method above. It now passes the post ID with the action and nonce values. We can use the post ID in our JavaScript file.

// ajax.js

var data = {
    'action': wp_ajax_data.action,
    'post_id': wp_ajax_data.post_id,
    'comment': comment_to_send
    '_ajax_nonce': wp_ajax_data.nonce
};

jQuery.post(ajaxurl, data);

Sanitizing our request data

With check_ajax_referer, we know when a request is valid. That said, we still don’t know know if the data we’re receiving is safe and valid. That’s where data sanitization comes in. WordPress has some handy built-in data sanitization functions that we can use.

A good way to handle the sanitization is by creating a unique method for each piece of data in the AJAX request. This allows us to handle sanitization and defaults on an individual basis. Let’s look at what this looks like inside AjaxHandler.

namespace MyPlugin;

class AjaxHandler
{
    // ...

    /**
     * Get the comment text sent by the AJAX request.
     *
     * @return string
     */
    private function get_comment()
    {
        $comment = '';

        if (isset($_POST['comment'])) {
            $comment = sanitize_title($_POST['comment']);
        }

        return $comment;
    }

    /**
     * Get the post ID sent by the AJAX request.
     *
     * @return int
     */
    private function get_post_id()
    {
        $post_id = 0;

        if (isset($_POST['post_id'])) {
            $post_id = intval($_POST['post_id']);
        }

        return $post_id;
    }
}

get_comment uses sanitize_title to sanitize the comment text. It returns an empty string if there wasn’t any comment sent in the AJAX request. get_post_id does the same thing, but uses intval to sanitize the post ID.

PHP also has a lot of tools for sanitizing your data. The most powerful one is the filter_var function that’s been available since PHP 5.2. It comes with a lot of handy filters. These make data sanitization a breeze. You can find our two methods converted to use filter_var below.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Get the comment text sent by the AJAX request.
     *
     * @return string
     */
    private function get_comment()
    {
        $comment = '';

        if (isset($_POST['comment'])) {
            $comment = filter_var($_POST['comment'], FILTER_SANITIZE_STRING);
        }

        return $comment;
    }

    /**
     * Get the post ID sent by the AJAX request.
     *
     * @return int
     */
    private function get_post_id()
    {
        $post_id = 0;

        if (isset($_POST['post_id'])) {
            $post_id = absint(filter_var($_POST['post_id'], FILTER_SANITIZE_NUMBER_INT));
        }

        return $post_id;
    }
}

Sending a response back

You can also have a two-way AJAX communication. That means that you need to send data back to the web page. In most cases, you can send the response data back in the handle method.

What you decide to send back can vary quite a bit. You can send a simple string back using echo or a larger piece of HTML. Below, we updated our handle method to echo a string back.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Action argument used by the nonce validating the AJAX request.
     *
     * @var string
     */
    const NONCE = 'my-plugin-ajax';

    /**
     * Handles the AJAX request for my plugin.
     */
    public function handle()
    {
        // Make sure we are getting a valid AJAX request
        check_ajax_referer(self::NONCE);

        // ...

        echo 'Science achieved!';
        die();
    }
}

You can also send data back in the form of a JSON payload. WordPress has some built-in functions to help you with that. They’re wp_send_json, wp_send_json_success and wp_send_json_error.

wp_send_json is a generic function for sending a JSON response. You use the other two when you’re using jQuery for your AJAX communication. They send extra data back so that jQuery knows if the AJAX call was successful or not.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Action argument used by the nonce validating the AJAX request.
     *
     * @var string
     */
    const NONCE = 'my-plugin-ajax';

    /**
     * Handles the AJAX request for my plugin.
     */
    public function handle()
    {
        // Make sure we are getting a valid AJAX request
        check_ajax_referer(self::NONCE);

        // ...
        
        if ($science instanceof WP_Error) {
            wp_send_json_error('Science failed...');
        }

        wp_send_json_success('Science achieved!');
    }
}

Using class methods for responses

You can also get complex scenarios with text, HTML or JSON. That’s when splitting the responses into their own methods can have benefits. For example, you could create a generic method for handling WP_Error objects.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Sends a JSON response with the details of the given error.
     * 
     * @param WP_Error $error
     */
    private function send_error(WP_Error $error)
    {
        wp_send_json(array(
            'code' => $error->get_error_code(),
            'message' => $error->get_error_message()
        ));
    }
}

That’s what the send_error method does. It creates a JSON response using WP_Error object. You could then reuse that method in other handler classes.

Solving abstract problems is about breaking the problem down

That’s the important takeaway from this article. You should always be looking to break down an abstract problem into smaller concrete problems. That’s what those tasks were. You saw common ones that could apply to any AJAX class.

I’ll leave you with the class with all its methods below.

namespace MyPlugin;

class AjaxHandler
{
    /**
     * Action hook used by the AJAX class.
     *
     * @var string
     */
    const ACTION = 'my_plugin';

    /**
     * Action argument used by the nonce validating the AJAX request.
     *
     * @var string
     */
    const NONCE = 'my-plugin-ajax';

    /**
     * Register the AJAX handler class with all the appropriate WordPress hooks.
     */
    public static function register()
    {
        $handler = new self();

        add_action('wp_ajax_' . self::ACTION, array($handler, 'handle'));
        add_action('wp_ajax_nopriv_' . self::ACTION, array($handler, 'handle'));
        add_action('wp_loaded', array($handler, 'register_script'));
    }

    /**
     * Handles the AJAX request for my plugin.
     */
    public function handle()
    {
        // Make sure we are getting a valid AJAX request
        check_ajax_referer(self::NONCE);

        // Stand back! I'm about to try... SCIENCE!

        die();
    }

    /**
     * Register our AJAX JavaScript.
     */
    public function register_script()
    {
        wp_register_script('wp_ajax', plugins_url('path/to/ajax.js', __FILE__));
        wp_localize_script('wp_ajax', 'wp_ajax_data', $this->get_ajax_data());
        wp_enqueue_script('wp_ajax');
    }

    /**
     * Get the AJAX data that WordPress needs to output.
     *
     * @return array
     */
    private function get_ajax_data()
    {
        return array(
            'action' => self::ACTION,
            'nonce' => wp_create_nonce(AjaxHandler::NONCE)
        );
    }

    /**
     * Get the comment text sent by the AJAX request.
     *
     * @return string
     */
    private function get_comment()
    {
        $comment = '';

        if (isset($_POST['comment'])) {
            $comment = filter_var($_POST['comment'], FILTER_SANITIZE_STRING);
        }

        return $comment;
    }

    /**
     * Get the post ID sent by the AJAX request.
     *
     * @return int
     */
    private function get_post_id()
    {
        $post_id = 0;

        if (isset($_POST['post_id'])) {
            $post_id = absint(filter_var($_POST['post_id'], FILTER_SANITIZE_NUMBER_INT));
        }

        return $post_id;
    }

    /**
     * Sends a JSON response with the details of the given error.
     *
     * @param WP_Error $error
     */
    private function send_error(WP_Error $error)
    {
        wp_send_json(array(
            'code' => $error->get_error_code(),
            'message' => $error->get_error_message()
        ));
    }
}

Ajax::register();
Creative Commons License