PHP and Go, Together at Last!

PHP and Go, Together At Last!
PHP and Golang, together at last!

This blog post is about Spiral Framework and Roadrunner Server. I’ll briefly talk about what they are, then show how to compile a custom Roadrunner server, start developing with Spiral, using Docker.

Explain Like I’m 5 PHP Developers

Roadrunner works by creating a HTTP server with Golang’s excellent net/http package, and using Goridge as a bridge to pass PSR7 Request and Responses between PHP and Go. The PHP application is then a long-running, already bootstrapped PSR7-capable application that received the already parsed PSR7 request, dispatches it, and collects the response to give back into Go. [1]

Roadrunner offloads unnecessary operations from PHP to a more optimized server, and effectively swaps out the classic setup of Nginx+FPM with a PHP/Golang application that boosts flexibility and performance. [2]

Roadrunner can serve static files without the presence of Nginx, therefore, simplifying the creation of Docker containers. [3]

You can extend your PHP application by including Go libraries, [4], writing Go HTTP middleware, [5], or tweaking and extending the Roadrunner server. [6]

Spiral is a PHP Framework with a customized Roadrunner server. The main difference is that, when you use Spiral’s version of Roadrunner, it comes with more out-of-the-box solutions for PHP developers, Ie.

It’s possible to download the server pre-compiled, but that takes away our power of writing Go code. In this tutorial we start from scratch.

Let’s Go!

The instructions are for Mac, and assume you already have Docker Desktop installed, but the same concepts should work for Linux, and probably Windows.

Step 1

Create a directory for your project.
(If you want, replace hello-spiral with some other name.)

mkdir ~/hello-spiral

Step 2

Create a subdirectory called ./server/ and copy these files into it:


cd ~/hello-spiral
mkdir server
cd server

Step 3

Download this Dockerfile into the root dir of ~/hello-spiral

The first stage compiles the app server, the second stage installs the Spiral skeleton app.

Step 4

Your file tree should look like this:

cd ~/hello-spiral; tree

Build a new Docker image:

cd ~/hello-spiral
docker build -t hello-spiral .

Step 5

Run the new Docker image:

docker run -it -p 8080:8080 -p 2112:2112 hello-spiral

Step 6

Go to http://localhost:8080 and verify that it works.


Tada! It runs, but how do we develop?

Step 7

Let’s copy all the PHP files that were successfully installed in the container to our host, then mount them.

While the container is still running from Step 5, in another shell, do:

cd ~/hello-spiral
docker ps

This command will output your container ID:

docker ps

Use your ID in the next command (replace 1f057ae4e473 with your own id):

docker cp 1f057ae4e473:/var/www/app/. src

In the shell tab that is still running the server, stop the server (ctrl-c), then restart with a slight variation of the command from Step 5:

docker run -it -p 8080:8080 -p 2112:2112 -v "$(pwd)"/src:/var/www/app:cached hello-spiral

Keep Going!

Your local PHP files are now mounted in ~/hello-spiral/src, start developing! Change your Dockerfile:

# Setup Spiral
# RUN composer create-project spiral/app . --no-scripts
# Or comment above, uncomment below, and copy Spiral 
COPY ./src/ .

If you make changes in ~/hello-spiral/server, rebuild!

Git Squash at the Command Line with a Bash Script

Useful if the commits on your branch are sloppy, and you want to clean them up, and you don’t mind that your new commits will be file based instead of time based.

What does that mean exactly? Let’s say you worked two days on 3 files. (One of the files was a class, another a config, and the third a unit test.) Over the course of two days you made 13 commits. Sometimes a commit was to one file, sometimes a commit was to both, sometimes all three… 13 times! (Sloppy! You should be ashamed.) After running the script below you will lose 13 commits but can restage and create either a single commit message, or up to 3 new commit messages: One for each file. (Clean! Dick Grune approved.)

Useful if you’re too lazy to use rebase with fixup. Instead, just run one cheap and barely good enough bash script and you’re done. So lazy in fact you’re scrolling through all these words not even reading them, get to the script already.

Useful in scenarios where GitHub’s Squash & Merge interface is unavailable.

Prerequisites before running the script below, where feature/foo is your branch:

  • The script is saved somewhere in your path.
  • You are working on branch feature/foo. You want to create a single squashed commit, or restage in batches, up to as many commits as you have files.
  • You have merged “destination” into feature/foo and resolved conflicts.
  • You are the author of all the commits on feature/foo (Or want to be the author? You will be rewriting…)
  • You are currently on feature/foo.
set -e
branch=$(git rev-parse --abbrev-ref HEAD)
git checkout ${destination}
git pull
git checkout ${branch}
git checkout -b ${branch}-backup
git checkout ${destination}
git branch -D ${branch}
git checkout -b ${branch}
git merge --squash ${branch}-backup
Everything seems fine. Next steps:
    git status
    git commit # Or re-stage and do multiple commits
    git push --force-with-lease --set-upstream origin ${branch}

To undo:
    git reset HEAD .
    git checkout -- .
    git checkout ${destination}
    git branch -d ${branch}
    # If branch exists on GiHub:
    git branch -D ${branch}-backup
    git fetch
    git checkout ${branch}
    # Else:
    git branch -m ${branch}-backup ${branch}
echo "$_done"

Source code.

After running this script your files will be staged. You can either rewrite a nice single commit message for all the files, restage and commit in batches, or undo.

Inspired by this Gist.

Machine Learning and AI using PHP

Montrealer’s who won’t let PHP go (pun intended), get in on that sweet venture capital? Here are two fantastic PHP options for doing machine learning (ML), artificial intelligence (AI), and unprecedented memorization (Singularity), in a dev stack we’re already pretty good at:

Rubix ML

Amazing tutorials like Iris Flower Classifier, Credit Risk Predictor, and Sentiment Analysis make all that buzzword soup turn into real code you can learn from and build on.


Winner of the prestigious Eastern European guy handing out money to projects he likes award, this library is solid clean code.

Other camps may have their sensible conventions, consistent syntax, math fundamentals… But we have PHP! Let’s go!

Pcov Is Better than Phpdbg and Xdebug for Code Coverage

Since the last time, I changed from phpdbg to pcov.

Results? pcov is faster, uses less memory, is more accurate than phdbg.


Time: 4.09 minutes, Memory: 3.32 GB


Time: 3.48 minutes, Memory: 1.36 GB

Caveat, only works with PHPUnit 8+… Or does it?

Because my project is a WordPress plugin, and because WordPress has unresolved issues older than my 9-year-old, they don’t support PHPUnit 8, yet.

Lucky for me, unlucky for the maintainer who would rather not support this gross hack, pcov works with PHPUnit 7, and probably PHPUnit 6, thanks to pcov-clobber.

Previously on (show name)

In June 2018 I ran into a problem using Xdebug for code coverage.

PHPUnit without Xdebug took 5 minutes. PHPUnit with Xdebug (to generate code coverage reports) took ~50 minutes. This was too long for Travis CI. The job would crash, abort, and never finish code coverage.

Fixed by switching to phpdbg. It ran 10x faster. Maybe the code coverage metrics were a bit worse but at least it ran.

All was well until it wasn’t. Last week phpdbg started crashing Travis CI with [PHP Fatal error: Out of memory].

I needed another solution. Enter pcov.

How to switch to pcov if you are stuck with PHPUnit 7

It all started here?

Or maybe here: Running for Coverage

Basically, this:

So if you are doing this:

vendor/bin/phpunit --configuration phpunit.xml --coverage-clover coverage.xml

Or this:

phpdbg -qrr -d memory_limit=-1 vendor/bin/phpunit --configuration phpunit.xml --coverage-clover coverage.xml

Simply change to:

pecl install pcov; composer require pcov/clobber; vendor/bin/pcov clobber; vendor/bin/phpunit --configuration phpunit.xml --coverage-clover coverage.xml

And you’re done.

Here’s my diff:

Refactor Your Slow Form Using PHP Generators and Event Streams

Your form will still be slow but the user experience will be better. They will see a progress bar and see status updates in real time. The idea is to refactor something like this:

 * A task that takes way too loooooooooooooooooooooooong...
function task() {

Into this:

 * Yields a key/value pair
 * The key is between 1-100 and represents percentage completed
 * The value is a string of information for the user
 * @return Generator
function taskGenerator() {
    yield 1 => 'Completed step 1';
    yield 2 => 'Completed step 2';
    yield 3 => 'Completed step 3';
    yield 100 => 'Completed step 100';
 * A task that takes way too loooooooooooooooooooooooong...
function task() {
    foreach (taskGenerator() as $percentage => $info) {
        // Do nothing, this is a compatibility wrapper 
        // that makes our generator work like a regular function

And this:

let evtSource = new EventSource(url);


Imagine you have this form somewhere on your corporate intranet:

Spinning beach ball of death. (Source code)

The user clicks submit. They wait, and wait, and wait. The task completes and they receive some feedback saying “everything seems fine”. It’s not particularly good but it does the job. Your company doesn’t have the resources, infrastructure, or competence to setup job queues and delegate these kind of tasks into the background. Everyone lives with it. The end.

Insert cliché “What if I told you” meme here.


It is possible to use PHP Generators and Event Streams to provide real-time feedback to the web browser.

The EventStream console in Chrome Browser.

With a bit of refactoring your old form could, instead, behave like this:

A status bar with real-time status updates. (Source code)

The heavy drinking in the new form is an EventEmitter class (Source code).

On the front end, the main changes were from this:

<input type="submit">


<p><input type="submit"></p>
<div id="sse-progressbar"></div>
<p id="sse-info"></p>

And some JavaScript near the end of the form:

$('#tpsreport').on('submit', function (e) {
    let formSubmitButton = $('#tpsreport :submit');
    formSubmitButton.attr('disabled', true);

    let form = $('#tpsreport');
    let actionUrl = form.prop('action');
    let eventSourceUrl = actionUrl + (actionUrl.includes('?') ? '&' : '?') + $.param(form.find(':input'));

    let evtSource = new EventSource(eventSourceUrl);
    evtSource.onopen = function () {
    evtSource.onmessage = function (message) {
        let bar = $('#sse-progressbar');
        let info = $('#sse-info');
        let data = JSON.parse(;
        switch (data.action) {
            case 'updateStatusBar':
                bar.progressbar({value: parseInt(data.percentage, 10)});
            case 'complete':
                if (data.error) {
                    bar.progressbar({value: false});
                } else {
                    window.location = actionUrl;
    evtSource.onerror = function () {
        $('#sse-progressbar').progressbar({value: false});
        $('#sse-info').html('EventStream Connection Error');

The JavaScript (and jQuery) snippet:

  • Targets the form with id tpsreport
  • Stops the form from submitting and instead
  • Appends all the form data as $_GET parameters to the form’s action URL then
  • Passes that to a new EventSource
  • Updates sse-progressbar and sse-info when it receives an event stream message
  • Redirects the user back to the action URL when complete

On the back end, the time consuming function was refactored into a generator that yields a key/value pair. The key is between 1-100 and represents percentage completed. The value is a string of information meant for the user. Once you have a generator that follows this convention, pass it to the EventEmitter. The browser will start receiving an event stream.

 * @return Generator
function loooooooooooooooooooooooongGenerator() {
    yield 10 => "Hey Peter what's happening. I'm going to need those TPS reports... ASAP...";
    yield 30 => "Ah, ah, I almost forgot... I'm also going to need you to go ahead and come in on Sunday, too. We, uhhh, lost some people this week and we sorta need to play catch-up. Mmmmmkay? Thaaaaaanks.";
    yield 50 => '...So, if you could do that, that would be great...';
    yield 60 => 'Excuse me, I believe you have my stapler.';
    yield 90 => 'PC LOAD LETTER';
    yield 100 => 'Success!';
$emitter = new \KIZU514\EventEmitter();
$emitter->emit( loooooooooooooooooooooooongGenerator() );

Key ideas:

  • It’s not necessary to wait until the request finishes, PHP can emit event-stream responses (SSE) back to the web browser while it is working on something.
  • PHP Generators are a relatively simple refactoring hack to get those responses back to the browser.
  • sleep() is only meant as an example of a function call that takes a long time to finish, don’t put sleep in your production code, you already knew this, I hope?


Howto Fix Headset, Headphones with a Microphone, on a Dell Laptop

This was annoyingly unobvious, but when you plug your headset into your audio jack and the MaxxAudioPro popup appears:

One of these rows is not like the other.

Click the word “Headset” (because it’s a button!):

Can you tell me which one it is?

If  you don’t see a MaxxAudioPro dialog when you plug your headphones into the computer, turn that feature on:

Dell Inspiron -> Start -> MaxxAudioPro

Phpdbg Is Much Faster Than Xdebug For Code Coverage

I work on a project that uses Travis CI to test and build against three jobs. (PHP 7.0, 7.1, 7.2) I recently ran into a roadblock where Travis would fail with “No output has been received” on the job that did code coverage using Xdebug. A screenshot of the last successful build before the failure:

PHP 7.0.8 runs the tests without Xdebug and PHP 7.1 runs the same tests with Xdebug + Code coverage.

Only one of our three jobs runs code coverage because it’s ridiculously slow. We were disabling coverage on the other jobs so that, in a worse case scenario, we could at least check a hotfix in under 5 minutes.

This system was working fine until a few days ago. The test suite has kept growing and we got to the point where Travis just wouldn’t run code coverage anymore.

We tried every trick in the book and the best I could get it down to, minus the other stuff required to build (git clone, install dependencies, yarn build, phpcs, …) was 42 minutes.

Hooray the build didn’t crash and it only took 42 minutes to check!

Enter Phpdbg

PHP 7.0.8 runs the tests without Phpdbg and PHP 7.1 runs the same tests with Phpdbg + Code coverage.

Down from 42 minutes to 4!



  • Travis CI docs say to do phpenv config-rm xdebug.ini but this crashes the build on environments where Xdebug is not installed. Fixed by conditionally checking if Xdebug is on:
- if php -v | grep -q 'Xdebug'; then phpenv config-rm xdebug.ini; fi
  • phpdbg didn’t work in PHP 7.2: /home/travis/.travis/job_stages: line 57: 8492 Segmentation fault (core dumped). Fixed by running phpdbg only on PHP 7.1:
- if [[ ${TRAVIS_PHP_VERSION:0:3} == "7.1" ]]; then phpdbg -qrr -d memory_limit=-1 vendor/bin/phpunit --configuration phpunit.xml --coverage-clover coverage.xml; fi
- if [[ ${TRAVIS_PHP_VERSION:0:3} != "7.1" ]]; then vendor/bin/phpunit --configuration phpunit.xml; fi
  • A test that uses exec didn’t work: Unable to fork […] Fixed by only running that test when phpdbg is disabled. As we have three jobs (PHP 7.0, 7.1, and 7.2) and two of them don’t run code coverage (PHP 7.0 and 7.2) the test itself still runs, we just lose a bit of code coverage when running against PHP 7.1:

 $runtime = new \SebastianBergmann\Environment\Runtime();
 if ( ! $runtime->isPHPDBG() ) {
  // // TODO: exec(): Unable to fork error when running phpdbg()
  • “The output is not the same…” Fixed by not giving a shit? Is there really 42 minutes of justifiable output difference between Xdebug and Phpdbg? Our codecov metrics remained the same. Our Travis build stopped failing.

I <? PHP

PHP7 Learning Resources

PHP made a lot of mistakes. Many are still in the language today. However PHP is 23 years old now. PHP7, released December 2015 is a huge improvement over older versions. PHP7 outperforms Ruby, Python, and many others. If you haven’t looked at it this year, do so. “PHP sucks” is mostly FUD now.

For PHP Developers:

For C Developers:

“You can do anything at zombocom with PHP. It’s shared nothing architecture makes it decent for web development.”

O’Doyle Rules!

The Need For Speed On My Dell Inspiron 7000

I got a Dell Inspiron 7000 preloaded with Windows 10 for Christmas. The defaults were terrible. Here’s how I tweaked it. A picture is worth a weekend of my life, wasted:

Dell Inspiron 7000

Disable Encryption

Encryption was significant factor as to why things were slow. The recovery key is stored on Microsoft’s servers which I consider a risk not a feature. I turned it off.

Update Everything

I started by updating everything. Even though my Dell was brand new I spent several hours waiting for Build 1709 updates (Windows 10 Fall Creators Update).

Uninstall The Whole Lot

Next, after reboots and “don’t turn off your computer” progress bars from the previous step, I uninstalled all software I hated. Bloatware, crapware, things I didn’t want, my new computer came with a lot of it.

Apt / Yum / Homebrew

This is a new computer with nothing on it so a good opportunity to start off on the right foot with an apt alternative. I installed Chocolatey.

Sudo Make Me a Sudo

I created a new local admin user, logged in as the new admin, and deleted the old one. Clean slate in a usable state.


Finally, I installed Shut Up Windows 10 and applied the recommended settings. Cortana was fun for five minutes, my seven year old thought it was cool, otherwise useless and drags the system down.