Building a Command Line Daemon in PHP to Emulate AWS SQSD

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 POSTing 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 POSTed 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.

About the Author

Gilbert Pellegrom

Gilbert loves to build software. From jQuery scripts to WordPress plugins to full blown SaaS apps, Gilbert has been creating elegant software his whole career. Probably most famous for creating the Nivo Slider.

  • newbie here: Would be possible to execute this php cli with a button on a HTML?
    Could you link some resources on how to do this?
    Thanks!

    • All of the things you can do in a PHP CLI script you can do via a PHP web page. So there is no need to create a CLI script if you want it to work via a web page. Just created a normal PHP script.

  • Josh McKee

    Great article, love that you covered from initial creation thru deploying as a package. I’ve built a few php cli apps for internal use by our dev team, mostly for things which are convenient to pipe in/out of, e.g. one which takes a CSV input and creates a migrations-ready sql output to patch records in to qa/production environments for a configuration hierarchy we use. Super nice to drag a file from Atom to an iterm2 window, /path/to/file | ./scriptname > /path/to/output.sql.

    • Thanks Josh. Yes that’s exactly the kind of use case that is ideal for CLI scripts.

  • Ben Coates

    Very interesting, thanks Gilbert! I can definitely see how this could be useful in dev environments. Seconding @Ángel, though, it would also be super awesome to be able to do the flipside of this, to be able to build php-based forms that execute command line scripts – I find myself running the same commands over and over. I dream of having a mini web based dashboard to control and automate some of my bash scripts.

  • look

    I would not call PHAR files a “binaries”. PHAR file is an archive (compressed or not) but definitely needs php to compile it while running and it’s not a binary – it’s a text file (open composer.phar and you’ll see)