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.
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 is an object which sends a message to the right queue.
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.
A serializer is an object which is responsible for:
- converting the message object to JSON for persistent storage
- converting the JSON back to message object for consuming the message by the Consumer.
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
Here, we have created a message with name
SendForgotPasswordEmail and sent it to queue
Now, we need to create a consumer which listens to the
Consuming more than one queues
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
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.