PHP is a cornerstone of the web. It is widely used and deployed in everything from simple blog software, to APIs and complex applications. A key feature of web-scale applications is that the components are loosely coupled to one another — and in PHP we’re increasingly building applications rather than websites.

One approach I often use to loosen the coupling between system components is to introduce a job queue to defer tasks to another part of the system, or to another system entirely. This article covers how to adapt an existing PHP application to create jobs on a queue using RabbitMQ.

The code samples here are taken from the Guestbook sample application; you can find the code at https://github.com/ibm-cds-labs/guestbook

The PHP Application

We have a very simple guestbook application that allows users to leave a message with their name. When a user saves a comment, we write it to the database. So far, so simple.

simple-guestbook-page

We’re going to add webhook functionality to this application, but we need to consider performance. We don’t want to send potentially large numbers of webhooks synchronously, causing the user to wait for feedback that their message was successfully saved. Instead, we’ll use RabbitMQ and simply add a job to it when a new message is saved, then process the webhooks later.

Set Up RabbitMQ

We’ll use a RabbitMQ instance from Compose.com for this example, but RabbitMQ is an open source project, so it can be installed on any compatible platform for production or development purposes. The example will work for any kind of RabbitMQ installation; just set your config values as appropriate.

From the Compose dashboard, choose Create Deployment, then choose RabbitMQ from the list on the left-hand side.

Choosing RabbitMQ for deployment on Compose.com

choose-rabbitmq

Once RabbitMQ has been deployed, the dashboard will show your instance’s connection details in the form of a URL string. Here’s what mine looks like:

amqps://[username]:[password]@sl-eu-lon-2-portal.2.dblayer.com:10406/lj-brilliant-rabbitmq

In order to use this information, we’ll need to split out the host, port and vhost portions. Compose.com also requires use of a certificate with its SSL connection, which is available when you click the Show certificate button below the connection details. Copy the text including the opening and closing lines, and paste it into a file (mine is called rabbit.cert). Notice under the main connection string that there is also a link to an admin interface. Open this link in a new browser tab so you can follow the action as we communicate with RabbitMQ.

Connect PHP to RabbitMQ

To make things easy for ourselves, we’ll use the amqplib/amqplib library in our PHP project, which we can install using Composer. Once you’ve installed it, or if you’re using it already in your project, run these commands:

composer require php-amqplib/php-amqplib
composer install

With this library added, we can connect to RabbitMQ with code like this:

require_once __DIR__ . '/vendor/autoload.php';

$ssl_options = array(
  'capath' => '/etc/ssl/certs',
  'cafile' => './rabbit.cert',
  'verify_peer' => true,
);

$connection = new PhpAmqpLib\Connection\AMQPSSLConnection (
  'sl-eu-lon-2-portal.2.dblayer.com',
  10406,
  'lorna',
  'secret',
  'lj-brilliant-rabbitmq',
  $ssl_options);

$channel = $connection->channel();

If you’re following along with the GitHub code, you’ll find this in the src/web/dependencies.php file where the rabbitmq dependency is defined.

Let’s work through these four sections in turn:

  • First we include the autoloader for the phpamqplib library that we installed via Composer.
  • Next the $ssl_options variable is created. This variable is only needed if your connection is over SSL, as it is with Compose. We need to include the path to the certificates on the server, then the specific certificate file we downloaded earlier.
  • Now the interesting part: we connect to RabbitMQ. The parameters here are host, port, username, password, vhost and then the array of $ssl_options. (See Compose’s article on Getting Started with RabbitMQ for more.) If you’re not using SSL, switch to the AMQPStsreamConnection instead. This approach uses just the first four parameters (host, port, username and password) and is used in the tutorials on the RabbitMQ site if you want to see a working example.
  • Finally, make the call to channel(). If you inspect the result of the call, you’ll see that it’s a PhpAmqpLib\Channel\AMQPChannel object and in this case, all is well!

At this point, PHP is ready to talk to RabbitMQ and we can move on to creating queues and putting messages onto them.

Send the Message

RabbitMQ is a powerful message broker, capable of much more advanced tasks than what we will ask of it today. Messages are passed into RabbitMQ by a “producer”, which in this example, is our PHP application. Queues are created and bound to “exchanges”, which deal with routing the messages to the right queues. In simple terms, there should be as many queues as there are copies of messages.

To keep things simple in this example, we’ll use the default exchange (the second argument to the basic_publish() method), and simply declare a queue and pass messages straight to it. The queues are storage, so the messages will then stay there until they are processed by a worker. (We won’t cover workers here.)

Messages themselves are just strings, so depending on the information to transmit and what will be consuming the message you can use any string format you like. In this example, we’ll simply use a JSON format to encode the guestbook message data of name, comment and a datestamp. Here’s the code for that section:

$channel->queue_declare(
    'comments',
    false, // passive
    true, // durable
    false, // exclusive
    false // autodelete
); 

$msg = new \PhpAmqpLib\Message\AMQPMessage(
    json_encode($comment),
    ["delivery_mode" => 2]
);
$channel->basic_publish($msg, '', 'comments');

In the Guestbook project, this code is in the add() method of the src/web/classes/Guestbook/CommentService.php file. It also handles the case where no queue is available.

There are a few separate steps going on here. The first is to create the queue itself, and since RabbitMQ is so configurable, there are quite a few options we can set to tell the queue how to behave. We only need a simple work queue, and so we leave most options unset (the various options are commented to make it easier to understand what each one does). For our purposes, we simply mark the queue as durable, which means that the contents will be written to disk and therefore survive a server restart.

Next, we prepare the message by creating an AMQPMessage object with our desired message string inside; in this case, it’s the JSON-encoded $comment data. This constructor also accepts an array of options. We’ll set the delivery_mode to 2 so that our message will be stored on disk (2 means persistent), since the queue we’re sending to is itself durable.

Finally, the message is published to the queue with the call to basic_publish(). By passing that empty second parameter we are using the default exchange, which has no name. To work with multiple exchanges, they would be created and the queue or queues bound to them with particular routing keys before we sent the message. Sending an empty exchange parameter causes RabbitMQ to use the default exchange and to route the message into the queue with the matching name.

If you’d like to see the complete code for this project, it’s on GitHub: https://github.com/ibm-cds-labs/guestbook

Adding RabbitMQ To Your Applications

Using this approach to delegate tasks, rather than processing them synchronously in PHP, is an important technique for building robust, scalable systems. Keeping the website or user interface responsive is key to surviving heavy traffic loads.

Putting the task into a system like RabbitMQ means that we have loose coupling between our application that writes the messages, and another system that actually processes them. That other system might run on other servers, protecting the resources of the user-facing site. System components may also be scaled independently of other parts of the system, and they also use other technology stacks. In this specific example, the messages are consumed by a Node.js application, since we have potentially large numbers of web requests to handle and Node.js is well suited to this task.

If you’d like to see a tutorial on how the Node.js component works, please let us know below in the comments. Thanks for reading!

Join The Discussion

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