WordPress is a great tool for many things though one of the challenges you may run into is handling time-consuming tasks such as sending emails, processing images, and running database queries. These tasks can slow down the website’s performance and lead to a poor user experience.

One way to address this challenge is to use asynchronous task processing. This involves executing time-consuming tasks in the background, separate from the main WordPress request-response cycle. This approach frees up the website to handle other requests while the task runs in the background.

RabbitMQ is a message broker that can be used to implement asynchronous task processing in WordPress. RabbitMQ provides a reliable and scalable platform for sending and receiving messages between different systems. It enables developers to create decoupled systems that can handle a large volume of messages and scale horizontally as needed.

In this article, we will explore how to use RabbitMQ for asynchronous task processing in WordPress during custom WordPress plugin development. We will specifically look at how to implement RabbitMQ in your WordPress plugin using the popular php-amqplib library.

Before you do any coding, you’ll need to install rabbitMQ. They have a pretty exhaustive guide on how to do this here.

Now on to the coding.

Add to your plugin. This can also be done in your functions.php file.

composer require php-amqplib/php-amqplib

composer require vlucas/phpdotenv

That done, add new environment variables to store your RabbitMQ connection details. Create a file named .env and add to it the following:

RABBIT_MQ_HOST='localhost'
RABBIT_MQ_PORT='5672'
RABBIT_MQ_USERNAME='guest'
RABBIT_MQ_PASSWORD='guest'

These are the default credentials when you first install rabbitMQ. This guide (starting at Step 3: Set Up RabbitMQ Administrative User) talks through how to change them.

Next, add this class to your plugin or theme.

<?php

use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;


/**
 * The RabbitMQ queue
 */
class My_RabbitMQ {
    
    private $queue_name = 'wordpress';

    public function add_to_queue( array $raw_message ){
        
        if(!isset($raw_message['action'])){
            return new \WP_Error( 'action_not_specified', __( "The 'action' key has not been specified", "my-plugin" ) );
        }
        $raw_message['site_name'] = get_bloginfo('name');
        $raw_message['site_url'] = get_bloginfo('wpurl');
        $connection = new AMQPStreamConnection( $_ENV['RABBIT_MQ_HOST'], $_ENV['RABBIT_MQ_PORT'], $_ENV['RABBIT_MQ_USERNAME'], $_ENV['RABBIT_MQ_PASSWORD'] );
        $channel = $connection->channel();
        
        $channel->queue_declare( $this->queue_name, false, true, false, false );
        $msg = new AMQPMessage( json_encode($raw_message) );
        $channel->basic_publish( $msg, '', $this->queue_name );
        
        $channel->close();
        $connection->close();
    }
 

}

This class makes it possible for you to add messages to your rabbitMQ queue. Notice that while adding messages, it checks that an action key has been set (more on that later) and then it appends the site name and URL to the message. These are useful if you’ll use the same queue for different WordPress websites. How do you make use of this class so that you can add messages to your queue? After the class has been included in your code, run:

            $rabbitmq = new My_RabbitMQ();
            $queue_message = [ 'action' => 'my_plugin_custom_action', 'key_one' => 'keyOneData, 'key_two' =>  'keyTwoData'];
            $rabbitmq->add_to_queue($queue_message);

That’s it. Your message with action my_plugin_custom_action has been added to your RabbitMQ queue. Now, you need a worker to pick up that message and do something with it. To do that, create a script rabbitmq-worker.php and to it add:

<?php

require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;

// Load WordPress 
require_once("../../../wp-load.php");

// Load env variables
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();

$connection = new AMQPStreamConnection( $_ENV['RABBIT_MQ_HOST'], $_ENV['RABBIT_MQ_PORT'], $_ENV['RABBIT_MQ_USERNAME'], $_ENV['RABBIT_MQ_PASSWORD'] );
$channel = $connection->channel();
$queue_name = 'wordpress';

$channel->queue_declare($queue_name, false, true, false, false);

error_log( 'kanzu-rabbitmq| [*] Waiting for messages. To exit press CTRL+C\n');

$callback = function( $msg ) {
  $raw_message = json_decode($msg->body);
  do_action($raw_message->action, $raw_message);
  error_log( "kanzu-rabbitmq| [x] Received {$msg->body}\n");
  $msg->ack(); // Acknowledge the message. 
};

$channel->basic_consume( $queue_name, '', false, false, false, false, $callback );

while( count( $channel->callbacks ) ) {
    $channel->wait();
}

$channel->close();
$connection->close();

This worker goes to the queue, retrieves a message and then calls the action you set in that message. Remember that when adding a message to the queue, we set an action my_plugin_custom_action . This worker will call any method attached to that action. The last piece to get your asynchronous code working is to attach a callback to the my_plugin_custom_action action. To do this, do it the usual way

add_action('my_plugin_custom_action','perform_custom_action');

function perform_custom_action( $args){
$key_one = $args->key_one;
$key_two = $args->key_two;
// Do whatever time-consuming, resource-heavy task you need to do
}

Run the worker on your server by running:

php rabbitmq-worker.php & 
disown

The worker writes to debug.log if you’ve turned debugging on. This is useful during development but you’d need to turn this off in production and write the logs to a more appropriate location.

Leave a Reply

Your email address will not be published. Required fields are marked *