Sometimes when you’re building a project there are parts of the architecture that exist on production that don’t exist on your development machine. Those missing parts (like proprietary software that’s specific to your hosting provider) can sometimes mean unwelcome surprises when you deploy to production.
Recently as part of my work on Mergebot, I decided to address this. My local machine was missing the AWS Elastic Beanstalk Worker Environment SQS daemon (known as SQSD). AWS isn’t open source so there’s, unfortunately, no official way to replicate it. So I decided to build a small PHP command line (CLI) app to attempt to replicate its functionality.
In this article, I’m going to cover some of the aspects of creating a command line app in PHP and explain how I implemented them for my replica SQSD CLI.
Laravel vs SQSD
First, a bit of explanation. The Laravel queue worker operates by polling a queue system (SQS in our case) for jobs and then runs them in sequence (as the queue worker is part of the application).
The AWS SQSD, however, is designed to be completely framework-agnostic so that it can be used by any application. It works by polling an SQS queue for jobs and then POST
ing them to an endpoint specified in your Elastic Beanstalk worker environment settings (default is http://localhost
). If a job fails for any reason, then the job is sent to what is called a Dead Letter Queue for manual processing.
The AWS SQSD also contains other functionality specific to Elastic Beanstalk worker environments. For example, it can read a cron.yaml
file in the root of your application which specifies periodic tasks that can be run on a schedule. SQSD will send a job to the queue every time the schedule is triggered. These jobs will then be processed by SQSD like normal but will be POST
ed to the path specified in the cron.yaml
with some extra headers. This is useful in an auto scaling environment where a single “cron” can send jobs to the queue and any server in the cluster can pick up the job and run it.
Creating a CLI in PHP
Creating a CLI script in PHP is actually simpler than you might expect. For example, if you create a file without the .php
extension (e.g. mycliapp
):
#!/usr/bin/env php
<?php
echo 'Hello World!';
Note that we add a Shebang on the first line, similar to a bash script. This simply allows you to run the script without specifically having to call the php
binary first on UNIX-based systems (but we’ll use php
in this article for clarity).
Next, you need to make the script executable:
chmod +x mycliapp
You can now run your PHP CLI by executing:
php mycliapp
Hopefully, you should see Hello World!
printed in your CLI. This simple file will act as the entry point for your PHP CLI app. In fact, if you’ve ever used Laravel’s artisan command, this is exactly how it works.
You might notice, if you look at the SQSD CLI app that it is using the Symfony Console framework. I’ve built several CLI’s in PHP and always used Symfony Console and can highly recommend it. I’m not going to cover everything it does in this article, but I suggest you spend some time looking through the docs to see what it can do.
Creating a Daemon
A daemon, in computing terms, is simply an application that runs a process in the background. The user might have no idea it’s running, but it will continue to run its process until it is stopped. As SQSD is a daemon, I needed to replicate this functionality in my PHP app.
I decided to have a look at how Laravel worker handles this “daemon” functionality and ended up implementing a similar but much stripped down version in the Worker class. The gist of the daemon
method is a while
loop that runs continually (until it is interrupted):
while (true) {
$this->isLeader = $this->isLeaderProcess();
if (!$this->daemonShouldRun()) {
$this->pauseWorker();
continue;
}
$function();
$this->sleep();
$this->stopIfNecessary();
}
The idea here is that the $function()
closure will be called every loop and then the process will sleep for a set amount of time (to avoid thrashing the CPU) before running the loop again. We can then use this Worker
class in the WorkCommand to create a daemon:
$worker = new Worker($options);
//...
return $worker->daemon(function () use ($sqsd, $worker, $output) {
if ($worker->isLeader) {
$sqsd->runPeriodicTasks();
}
$sqsd->checkForMessages();
});
Packaging the CLI App
The final piece of the puzzle for creating a CLI app in PHP is how to package it in such a way that it is easy to use.
First, we need to compile the app into a binary so that it can be used as a standalone package without having to manually install dependencies. PHP calls these binaries Phar archives. You’ll probably have used a Phar in the past, even if you didn’t realize that’s what it was (for example, Composer is distributed as a Phar).
Building Phar archives manually can be a bit of a hassle, so I prefer to use the box2 app to make the process of building them really easy. With box2 you simply define a box.json
file in the root of your project (here is the one I created for SQSD), specify some options, and then you run box build
to create the Phar archive. Simple.
Now that we have our binary file we could stop there and tell people to download the Phar manually and use it as is. But what if people would like to install the SQSD package in their project using Composer? Thankfully Composer has a “bin” key that allows us to specify if our package contains binaries. If so, it symlinks the binary to the vendor/bin
folder so that it can be run like this:
php ./vendor/bin/sqsd.phar work
Now we can tell people to install our package like any other composer package (e.g. composer require gilbitron/sqsd
) and the binary will be available to run as the above command.
As a side note, If you’re looking for a package that will make Laravel work with SQSD I can recommend the dusterio/laravel-aws-worker package.
Conclusion
The aspects of building a CLI app in PHP that I’ve briefly covered in this article are the foundation for most CLI apps. Obviously your own apps will have different requirements and work in different ways, so feel free to take what I’ve done here and build on it to make it your own (if you do, definitely come back here and link us in the comments!).
If you’re interested in digging a bit deeper, I recommend taking a look at the SQSD source code and looking at how I implemented the more intricate pieces of the SQSD CLI.
Have you ever created a CLI app in PHP? Have you ever used Symfony console of box2 before? What else do you wish you could replicate on your local machine? Have you got any tips or tricks for creating CLI apps in PHP? Let us know in the comments.