Petr Pabouček
Freelance Web Developer

Prohlédněte si seznam mnou nabízených služeb, podívejte se, co o mě říkají mí klienti, či na jakých projektech jsem se podílel. V případě zájmu o spolupráci, či jakýchkoliv jiných dotazů, mě prosím kontaktujte.

Efficient handling of AJAX requests on WordPress platform

WordPress has got built in very developer friendly way for handling AJAX requests, which is in detailed described in WordPress documentation.

All AJAX requests are basically sent to /wp-admin/admin-ajax.php along with some data:


(function($) {

    var data = {
        'action'     : 'my_action_name',
        'postId'     : 5,
        'someKey'    : 'someValue',
        'anotherKey' : 'anotherValue'
    };

    $.get( '/wp-admin/admin-ajax.php', data );

})( jQuery );

Please note the parameter action here. This is the important ingredients which creates connection between the javascript code and your WordPress php code.

The fact, that AJAX request is triggered against /wp-admin/admin-ajax.php allows us to catch this action in our back-end using wp_ajax_{action} or wp_ajax_nopriv_{action} action hooks and call php function, which is going to do something with the data sent.

Logic is demonstrated on an example below.

<?php
/*
* Fires AJAX action for authenticated users
* https://developer.wordpress.org/reference/hooks/wp_ajax_action/
*/
add_action( 'wp_ajax_my_action_name', 'doSomethingWithData' );

/*
* Fires AJAX action for non-authenticated users
* https://developer.wordpress.org/reference/hooks/wp_ajax_nopriv__requestaction/
*/
add_action( 'wp_ajax_nopriv_my_action_name', 'doSomethingWithData' );

/*
* PHP function triggered by AJAX request
*
* This is the part which I am finding really handy as all
* standard WordPress functions can be used here in a same 
* way, like you would do when working inside the theme or 
* plugin.
*/
function doSomethingWithData()
{
    $postId          = intval( $_GET['postId'] );
    $someValue       = sanitize_text_field( $_GET['someKey'] );
    $anotherValue    = sanitize_text_field( $_GET['anotherKey'] );  

    // We can for example store data as post meta using update_post_meta WP function
    update_post_meta( $postId, 'someKeyName', $someValue );
    update_post_meta( $postId, 'anotherKeyName', $anotherValue );

    wp_die();
}

Whole principle is demonstrated on figure below.

Figure showing demonstration of principle of WordPress admin-ajax.php

This approach works great for many use cases, but it’s becoming less convenient when it comes to handle higher traffic.

In this article I am going to explain one of the possible ways, how to implement AJAX more efficiently. It is meant especially for developers who work with WordPress platform and care about performance, but idea presented is in fact generic and can be used elsewhere.

A few years back I was tasked to create a platform for management of simple polls widgets and its distribution across the partner sites. Since WordPress is often good starting point for many projects, this was my obvious choice in this case as well. You can look at WordPress as a framework, which comes with handy features such as:

  • User management – login, registration, password recovery, user roles
  • Media management – uploads, thumbnails generation
  • User friendly interface for managing content

Simply speaking, instead of starting to code everything from scratch, you can use WordPress as a starting point and focus on actual core issues by building required functionality on the top of WP.

In this specific project, each poll widget was implemented as a custom post type, with following attributes:

  • title – represents poll question
  • thumbnail – featured image
  • meta – used for storing number of votes per each answer. The only answers allowed were simply just Yes or No.

So far, so good. The tricky bit comes, when you are expected to handle loads of votes at the same time. Solution I’ve came up with proved itself to work really well and I used same logic in other projects since then.

In this article, I am going to demonstrate the logic on simple example of WordPress plugin which introduces efficient handling of ‚page-views‘ counter for articles published. Everything we will go through in this article can be downloaded as a plugin via official WP repository.

Outlining solution

  • Storing view counters outside of WordPress ecosystem using fast storage back-end.
  • Triggering AJAX request.
  • Moving counters from temporary storage into database.
  • Showing page-views.

Storing view counters outside of WordPress ecosystem

Traditional approach would be to trigger AJAX request to /wp-admin/admin-ajax.php endpoint with defined action. This request would be then processed via either wp_ajax_{action} or wp_ajax_nopriv_{action} hook depending on if you want to handle request just for logged in users, logged out users or both.

Problem is, that /wp-admin/admin-ajax.php loads whole WP core and therefore this can become pretty resources consuming, if there are too many requests fired at the same time. This is surely something, what can easily happen during the traffic spikes.

Therefore it’s better approach to store view counters ‚outside‘ of WordPress temporarily and then regularly run checks for new counters and its values.

In my solution I’ve used PhpFastCache library which allows for easy interaction with different back-ends. As a temporary storage we can use for example fast MemcacheD or Redis storage which are easy to install and often available even on shared hosting.

In our plugin directory we create file ajax/counter.php where core idea is to create unique identifier for each post counter and increment it anytime article is visited.

<?php

/**
 * Instantiate PhpFastCache using memcached storage
 */
$cacheInstance = CacheManager::getInstance( 
  'memcached', 
  new \Phpfastcache\Drivers\Memcached\Config([
        'host' =>'127.0.0.1',
        'port' => 11211
]);

/**
 * Create unique identifier for each post
 */
$key = "post-" . intval( $_GET['postId'] );

$cache = $cacheInstance->getItem( $key );

/**
 * If item doesn't exist in cache, create new item, otherwise just increment existing item in cache
 */
if ( is_null( $cache->get() ) ) 
    $cache->set(1)->expiresAfter(3600)->addTag( 'pageviews_counter' );       

else 
    $cache->increment();

$cacheInstance->save( $cache );

Triggering AJAX request

In previous step we’ve prepared AJAX endpoint to handle storage of view counters outside of WordPress ecosystem. Now we just need to trigger request against this endpoint anytime article is loaded.

To enqueue our Javascript onto WordPress page, we use wp_enqueue_scripts action hook and following function:

<?php

public function enqueueScripts()
{
    global $post;

    // We want to count page-views just for published articles      
    if ( ! is_singular() || $post->post_status != 'publish' )
        return;

    // Enqueue our custom JS file stored in assets/src/js/ directory
    wp_enqueue_script( 
        'pageviews-counter',
        BPVC_PLUGIN_URL . 'assets/src/js/pageviews.js',
        [ 'jquery' ],
        BPVC_PLUGIN_VERSION,
        true
    );

    // Define ajax_url and postID variables 
    wp_localize_script(
    'pageviews-counter', 
    'pc_vars',[
            'ajax_url'  => BPVC_PLUGIN_URL . 'ajax/counter.php',
            'postID'    => $post->ID
        ]);
}

Actual pageviews.js file included in previous step can be something simple as this:

(function($) {

    var data = {
        'action'    : 'pageviews_count',
        'postId'    : pc_vars.postID
    };

    $.get( pc_vars.ajax_url, data );

})( jQuery );

Just to quickly summarize, we’ve created custom AJAX endpoint pc_vars.ajax_url, which is pinged anytime article is loaded. There’s created page-views counter for each post temporarily living in cache. With this approach we’ve bypassed triggering of /wp-admin/admin-ajax.php.

This means we are not loading WP Core during every AJAX request and we’ve got all the back-end processing fully under our own control.

Moving counters from temporary storage into database

At this stage we’ve got counters for each post living in temporary storage. We need to introduce mechanism for moving them from temporary storage into database to store page-views permanently.

WordPress cron system is perfect fit for this task and it comes with following predefined schedules:

  • Hourly
  • Daily
  • Twice Daily

Since we probably want to update page-view counters more often, we need to define custom cron schedule.

Hooking function below into cron_schedules filter will create 5 minutes schedule

<?php

public function addCronSchedules( $schedules ) 
{
    $schedules['five_minute'] = array (
                'interval'  => 300,
                'display'   => 'Once in 5 minutes'
        );      

    return $schedules;
}

Now we need to create task, which is going to be triggered in five minutes intervals and move votes from temporary storage into database.

Let’s setup re-occurring cron task using 5 minutes schedule created in previous step:

<?php

public function scheduleCronEvent()
{
    if ( ! wp_next_scheduled( 'pc_five_minute' ) ) 
        wp_schedule_event( time(),  'five_minute',  'pc_five_minute' );
}

In previous step we’ve created pc_five_minute action, which is triggered every five minutes. Now we need to create a function which is going to do following:

  1. Loop through all counters stored in Memcache
  2. For each counter get number of views
  3. Store views into database as postmeta for each respective post
  4. Remove counter from Memcache
<?php

public function storeViews()
{
    $cacheInstance = $this->model->setCacheManager();

    $counters = $this->model->getAllCounters( $cacheInstance );             

    if ( ! $counters )
        return;     

    foreach( $counters as $counter )
    {                       
        $key = $counter->getKey();                      
        $views = $this->model->getViews( $cacheInstance, $key );            

        if ( ! $views )
            return;

        $result = $this->model->updateViews( $key, $views );

        if ( $result )
            $cacheInstance->deleteItem( $key );
    }
}

All we need to do at this stage is to just hook storeViews function into pc_five_minute cron action.

<?php
/**
 * Custom cron action. Move counters from temporary storage into DB
 */
add_action( 'pc_five_minute', 'storeViews' );

Assuming you’ve got healthy cron system working on your site, this will ensure, page-views are updated in 5 minutes intervals.

Showing page-views

As described in previous paragraphs, at this stage we’ve got number of page-views stored as a postmeta value for each post. Displaying page-views in your theme is then just a matter of fetching relevant postmeta.

Since I’ve named meta key as ‚_pageviews‘, one of the possible ways how to get number of page-views on article page and print that out in template could be achieved by calling get_post_meta WordPress function.

<?php

echo get_post_meta( $post_id, '_pageviews', true );

Your Comment