WordPress Ajax - An object oriented way

Today I’d like to share with you a more object oriented way to handle ajax within WordPress.

"Classic" Way

I’ve been doing a lot of ajax with WordPress, I’ve done it the “original” way where you put the code in your functions.php and it would look like this :

<?php
function add_js_scripts() {
    wp_enqueue_script('script', get_stylesheet_directory_uri().'/js/script.js', array('jquery'), '1.0', true);

    // pass Ajax Url to script.js
    wp_localize_script('script', 'ajaxurl', admin_url('admin-ajax.php'));
}
add_action('wp_enqueue_scripts', 'add_js_scripts');

add_action('wp_ajax_my_action', 'my_action');
add_action('wp_ajax_nopriv_my_action', 'my_action');

function my_action() {
    $param = $_POST['param'];
    echo $param;
    die();
}

And then you would put your jQuery ajax this way :

$.ajax({
    url: ajaxurl,
    method: 'POST',
    data: {
        action: 'my_action',
        username: 'Frast',
    },
    success: function (response) {
        console.log(response);
    },
});

This approach always worked (and always will, I guess), but we can spot few drawbacks using procedural way, like :

  • It's polluting the functions.php, what happens when you’ll get 2, 5, 15 ajax actions ? (To avoid this, I’ve always split my actions into different files to stay organized)
  • You’ll have to edit a lot of text (function name, your javascript file name maybe, your actions name, ...) in the case scenario where you need to edit your action name
  • You’ll repeat a lot of the same code most of the time
  • You’ll maybe wrap your code into some conditions if you want to load it only in certains scenarios
  • If you have more than one action and so you call wp_localize_script() more than once, it just duplicate code, which is not very useful (in ajax case, you’ll always need to bind it to admin-ajax.php)

Going OOP

To simplify all of this, I’ve made a small package that you can install with composer :

composer require frast/wp-aoo

(It’s about 2 files to be honest)

Once installed, you can go to your functions.php and require your composer autoload like this :

require(__DIR__ . '/vendor/autoload.php');

For the next part, I’ll make the assumption that you created a src/ folder in your theme and inside it you’ll make the class MyAction inside another Ajax/ folder.
Also, for organization sake, you’ll make another file called loadAjax.php in the src/ folder and require it from the functions.php (after the autoload require).
Finally, you’ll create the js file linked to the ajax action, we’ll call it MyAction.js

Your theme directory should look like this:

- assets/
    - js/
        - MyAction.js
- src/
    - Ajax/
        - MyAction.php
    - loadAjax.php
- vendor/
    - frast/
    - autoload.php
- functions.php

Inside your class MyAction the most basic code could be :

<?php
# src/Ajax/MyAction.php

namespace App\Ajax;

use Frast\AjaxHandler;

class MyAction extends AjaxHandler
{
	/** @var array I specify that my js script will need jQuery as a dependency */
	protected $dependencies = [
		'jquery',
	];

	/**
	 * Return the path of the javascript file binded to the action
	 */
	public function getAssetSrc(): string
	{
		return get_stylesheet_directory_uri() . '/assets/js/MyAction.js';
	}

	/**
	 * How the action is going to handle the requests
	 */
	public function treatment(): void
	{
		// Do your stuff here, don't forget to sanitize user inputs in real case scenario
		$username = $_POST['username'];

		// JSON Response sent back
		wp_send_json([
			'message' => "Hello {$username} !",
		], 200);
	}
}

Inside MyAction.js :

// assets/js/MyAction.js

jQuery(document).ready(function () {
	// console.log(WP_ADMIN_AJAX);
	// console.log(MyActionNonce);

	jQuery.ajax({
		url: WP_ADMIN_AJAX, // This is globally available, it resolves to the admin ajax url
		method: 'POST',
		data: {
			// The action must be the same as the name of your php class
			action: 'MyAction',
			// For security reason, you must specify the nonce created for your php class
			// You can get it by suffixing "Nonce" to your php class, like so
			nonce: MyActionNonce,
			// Then send whatever data you like
			username: 'Stranger',
		},
		success: function (response) {
			console.log(response);
		},
	});
});

And finally to load your MyAction handler, put this in your loadAjax.php :

<?php
# src/loadAjax.php

use Frast\AjaxLoader;
use App\Ajax\MyAction;

// We register our ajax handlers
(new AjaxLoader())
	->register(MyAction::class)
	->load()
;

You can try it as is, it should work ! (hopefully :p)
By the way, don't forget to autoload your App namespace (from src/) in your composer.json, like so :

{
    "require": {
        "frast/wp-aoo": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}

And then to refresh the autoload with the command composer dump-autoload
You can find a full demo of the package here

How it works

The class MyAction declared two methods :

  • getAssetSrc() : It returns the path of the js file that the action is binded to
  • treatment() : It treats the request made on this action

Where you would normally put wp_ajax_nopriv_my_action and wp_ajax_my_action, MyAction inheriting from Frast\AjaxHandler will automatically do it for you based on the class name.
It will also call wp_enqueue_script() with the arguments that you defined. (see customization below)

After that we register our class within loadAjax.php with :

(new AjaxLoader())
    ->register(MyAction::class)
    ->load()
;

This simply register all of the handlers we want and then load them for WordPress to use them. (which will call the add_action() and wp_enqueue_script() with the config you gave to each handler)

All the code is available here and is free to use for literally anything

This is quite a small library so it would’nt be too long to grasp how it works.

Why would I use this ?

By using this approach you get :

  • A clean way to organize your files
  • Most of the time files will stay pretty small, but if it grows, your logic will remains inside a well defined class (but keep in mind to stay a bit SOLID)
  • If another developer have to catch up with your code, he can instantly see what you’ve loaded, which js file you’re querying, …
  • You can define concise conditions for loading (see customization below)
  • You can define clearly dependencies, version and in_footer if needed
  • You could automate even more things, like how the binded javascript file is fetch by extending the AjaxHandler class and specify an ajax javascript folder and then automatically fetch a javascript file that has the same name as your php class. That is an example of how you could easily take profit from doing OOP way
  • ...

It also encourages you to go a bit more object oriented inside WordPress development since a lot of tutorials and way of doing are more procedural-like.

Customization

Sometimes, you would like to load an handler only if certains conditions are met.
You can do that by declaring this in your handler class :

/**
 * Based on the return value, it will enable or not the ajax action
 */
public function conditions(): bool
{
	// You can also access WordPress functions from here
	$canLoad = is_home();

	return $canLoad;
}

Based on the return value, it will load or not your handler, saving some computation time and a browser request. (since it won’t wp_enqueue_script() your js script)

Also, since you can specify optionnals arguments to wp_enqueue_script(), you can redefine them with :

/** @var array */
protected $dependencies = [];

/** @var string|bool|null */
protected $version = false;

/** @var bool */
protected $inFooter = true;

Inside your handler class.

You can basically override anything from your handler class if needed.

Conclusion

This package can help you to better organize your files and logic on larger project (even if I use it on small project aswell) and this is quite valuable when you have to maintain projects !

I've also made a demo project that is available here which contains few examples of how you can profit from this package.

I hope that you can find any utility to this quite small package and aswell that it will motivates you to go a bit more into OOP if it's not already the case ! :)

May 30, 2019