Elegant background jobs in PHP

Ujjwal Ojha
Oct 18, 2017 · 3 min read

For a long time, I was looking for some elegant solutions to create and run background jobs in PHP.

I joined a company which used Laravel massively and Laravel ships with an awesome Queue system. Suddenly, I forgot how tedious and troublesome it used to be for me to write queues in PHP.

But, Laravel’s Queue seems to be tightly coupled with Laravel. We run out of such elegant solutions for applications that don’t use Laravel framework.

Bernard to the rescue. Bernard is a powerful PHP library for creating and running background jobs in PHP. According to its intro in Github repo:

Bernard is a multi-backend PHP library for creating background jobs for later processing. Bernard makes it super easy and enjoyable to do background processing in PHP.

But, configuring Bernard is not easy and the documentation is not clear enough. So, this is my attempt to get started with configuring Bernard.

Concepts

Message

A message is an object of any class implementing Bernard\Message which is added to background queue. It represents the job to be performed.

Producer

Producer is an object which sends a message to the right queue.

Consumer

Consumer is an object which takes messages from a queue and sends the message to the message receiver for handling the task specified in the message.

Serializer

A serializer is an object which is responsible for:

  1. converting the message object to JSON for persistent storage
  2. converting the JSON back to message object for consuming the message by the Consumer.

Driver

Drivers are pluggable message broker system responsible for actually sending the message from producers to consumers. In Bernard, a Driver implements Bernard\Driver. There are various implementations based on Redis, RabbitMQ etc. Check the Drivers page in the official documentation for the full list. Here’s a simple example of constructing driver based on predis/predis:

Simple Example

Producing Message

Here, we have created a message with name SendForgotPasswordEmail and sent it to queue emails.

Consuming Message

Now, we need to create a consumer which listens to the emails queue.

Consuming more than one queues

Advanced Example:

Suppose, we want to communicate using Message Objects of our own class like this:

All good till now. But, Bernard needs to convert the message object to JSON so that it can persist the message in the appropriate queue in JSON format. By default, Bernard only supports Bernard\Message\PlainMessage.

By default, Bernard can only normalize Bernard\Message\PlainMessage . We need to configure custom normalizer to normalize our custom object.

We added a new ObjectNormalizer which can normalize any object. You need to install symfony/property-access package to use this normalizer.

Now, let’s create a handler class for handling the task.

Assigning a callable job handler for each message class is quite tedious. What if we could create a map of message class to its handler class like given below and load the handler on demand.

Now, we can try to resolve the handler object from a dependency injection container. For this post, I am going to assume that the dependency injection implements PSR-11 ContainerInterface. But, the example given below will be similar for most cases.

Now, here’s how our handler class will look like:

Looks, pretty clean and above all Elegant.

Personally, I prefer to create an abstract class AbstractMessage class which returns message name based on full class name as shown below:

You can also use attach listeners to EventDispatcher in the consumer script as shown below:

The code for the final result is available in this repository.

Please, feel free to hack around and you can comment if you have a better approach for handling the situation.

DevCupBoard

A board full of secret sauce

Ujjwal Ojha

Written by

Software Developer, passionate LFC fan and a cricket lover!

DevCupBoard

A board full of secret sauce