Native Binaries with PHP

by | Jun 12, 2024

There is always a big debate about whether interpreted or compiled languages are more useful. I think it is important to look at the pros and cons.

Both language types have their strengths and weaknesses. While interpreted languages are great for maintaining and modifying software, compiled languages usually outperform them in terms of performance and packaging. It is also easier to distribute compiled binaries if they have been built to run on the desired architecture, instead of requiring to have a proper runtime environment installed on such systems.

 

Hybrid approach

There are loads of projects out there that aim to achieve that goal, in the huge world of interpreters, compilers and linkers.
One well-known candidate in that field is Nuitka. It is a transpiler for the Python language, which essentially compiles Python source code to C source code. It does this in a very clever way and optimises a lot of Python-specific quirks.
You can also build standalone packages, which include the compiled C code, an embedded CPython interpreter and the linked library files used by the program.

Just a heads up: Icinga’s current development stack includes the interpreted language PHP. It has established itself to be a very stable foundation over the last 29 years. We mostly use it for our web interfaces, with some exceptions being background daemons or Rest API endpoints.
You might already have an idea of what I’m getting at, but don’t be afraid – it’s not as bad as you would think 😉

 

What if we could compile PHP code into standalone binaries?

This would give us the option of creating command-line applications using the PHP ecosystem.
Some examples of such programs could be installers, debug tools, migration helpers, and so on.

 

Evaluating appropriate tooling

So what’s the fuzz about, can we do it?
The answer would be: yes and no. While there isn’t a transpiler that’s ready to use, one project is trying to solve the single-file distribution issue.

I’m talking about PHPMicro and static-php-cli. To be more specific, PHPMicro provides a self-executing server API for PHP, and static-php-cli offers PHP runtime builds with statically linked extensions.
The combination of these two approaches allows you to create a standalone application that contains the PHP runtime and all of the required extensions to run the code that you ship with it.

Although this option doesn’t offer any performance or runtime optimisations, it does make it simpler to distribute PHP scripts and eliminates the need for a compatible PHP runtime environment on the executing system.

 

Building a self-executing PHP application

Finally, I’d like to show you an example of how this build process works.
We start out by installing the spc tools:

curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
chmod +x ./spc

For the next step, you want to use spc’s doctor feature. It checks your system configuration and validates the needed build essentials.
If your system is missing something, just follow the instructions.
It should look like the following, once everything is ready to build:

./spc doctor
     _        _   _                 _           
 ___| |_ __ _| |_(_) ___      _ __ | |__  _ __  
/ __| __/ _` | __| |/ __|____| '_ \| '_ \| '_ \ 
\__ \ || (_| | |_| | (_|_____| |_) | | | | |_) |
|___/\__\__,_|\__|_|\___|    | .__/|_| |_| .__/   v2.2.3
                             |_|         |_|    
Checking if current OS are supported ... Linux x86_64, supported
Checking if necessary tools are installed ... ok
Checking if musl-wrapper is installed ... ok
Checking if musl-cross-make is installed ... ok
Checking if cmake version >= 3.18 ... 3.27.9
Checking if necessary linux headers are installed ... ok
Doctor check complete !

We can then continue to initialise the php-runtime and the extensions that we want to use with it. You can use a handy site to generate the desired spc command.
In this example, we just select the zlib extension and set the build target to micro.

For the build options, you want to select the following:

  • Build Environment:                   Native build (standalone scp binary)
  • Select build architecture:           x86_64
  • Download PHP version:                8.3
  • Enable debug message:                No
  • Enable ZTS:                          No
  • Download ... extension dependencies  Yes
    
  • Enable UPX compression               No

This should give you these two commands:

./spc download --with-php=8.3 --for-extensions "zlib"
./spc build --build-micro "zlib"

Now that the static runtime has been compiled, we are able to merge it with our PHP source code.
For this, I am using a small PHP script that reads the linux average load out of /proc/avgload and provides it back to the standard output (stdout):

<?php

// grab operating system (uname -s)
$os = php_uname('s');
if ($os !== 'Linux') {
    echo "Failure: Unsupported operating system" . PHP_EOL;
    exit(2);
}

// read /proc/loadavg
$handle = fopen('/proc/loadavg', 'r');
$info = fgets($handle);
fclose($handle);
if (! $info) {
    echo "Failure: Failed reading load averages" . PHP_EOL;
    exit(2);
}

// parse loadavg output
$info = explode(' ', trim($info));
if (sizeof($info) < 3) {
    echo "Failure: Wrong format for 'loadavg'" . PHP_EOL;
    exit(2);
}

// print load averages
echo "Load (1 minute): {$info[0]} | Load (5 minutes): {$info[1]} | Load (15 minutes): {$info[2]}" . PHP_EOL;
exit(0);

At last, we can merge the self-executing PHP runtime with our source code:

./spc micro:combine Load.php -O load

This spits out a self-executing binary that contains the needed runtime and its extensions to run your code on any system of your chosen architecture:

file ./load && ./load
./load: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
Load (1 minute): 1.83 | Load (5 minutes): 2.61 | Load (15 minutes): 2.58

 

What do we gain from this?

This was just to show you what’s possible – at the moment we are not considering using this technique for Icinga, because frankly it just doesn’t make a lot of sense. But I hope that I helped you learn something new today!

You May Also Like…

Releasing Icinga DB v1.2.1

Releasing Icinga DB v1.2.1

Today we are releasing a new version of Icinga DB, version 1.2.1, a maintenance release that addresses HA issues and...

Subscribe to our Newsletter

A monthly digest of the latest Icinga news, releases, articles and community topics.