Static PHP CLI

Recently, I've been playing around with NativePHP a Laravel package that is currently in alpha pre-release state. It bridges Laravel and Electron (Tauri support is currently in the making), thus allows building Native Desktop Apps for Windows and MacOS. In short: NativePHP allows you, to turn PHP Applications into Desktop Apps. During build, NativePHP bundles a PHP binary together with PHP Code. The binary executes PHP just like a server would. It needs to be specific for Operating System and Processor Architecture.

Instead of diving into NativePHP here, what caught my attention is the package that compiles the PHP binary. When developing a PHP Application, developers basically have two choices:

  1. Install PHP locally to your computer and use it (which is what Laravel Valet, Laravel Artisan or Symfony CLI do)
  2. Use a containerised development environment (for example using DDEV, Docker and the like)

From time to time, it’s necessary to add PHP Extensions to PHP which means you need to compile it for your platform and architecture and finally enable the PHP extension.

If you’re going with option 1 then you need to install these manually. If you’re going with option 2, chances are, you’re using images that got you covered, if not you would install these manually – but you would do so once inside the docker configuration.

Some common project requirements, which lead to the need to install a PHP extension can be: Yaml to parse YAML configuration files, Redis for caching, SSH2 for when you need to make secure connections to third party servers or just Imagick to manipulate images or create thumbnails.

Luckily, there’s tools like PECL and PHIVE that help you install PHP Extensions:

pecl install redis
pecl install yaml
pecl install ssh2
pecl install imagick

That’s the theory, in practice it can get a bit more complicated though – and by that, I am referring to manually downloading an extension and installing it via ./configure && make && make install, which, if we’re being honest, isn’t that much of a hassle either.

Now with static-php-cli there’s a third way of running PHP. This is similar to what FrankenPHP is doing. The idea is to bundle a pre-compiled binary together with your app. You can do this with a composer run script or a Makefile, for example, that builds the php binary for you.

By doing so, you don’t need to worry about colleagues having to install php extensions by themselves.

In a Composer-based PHP Application, you would simply composer req crazywhalecc/static-php-cli and then add a run script like this:

"scripts": {
  "build": [
    "test -f bin/php && rm bin/php || true",
    "test -d .cache/build || mkdir -p .cache/build",
    "cd .cache/build && ../../vendor/bin/spc download --with-php=8.3 --for-extensions 'filter'",
    "cd .cache/build && ../../vendor/bin/spc build --build-cli 'filter'",
    "cd .cache/build && mv buildroot/bin/php ../../bin/php",
    "bin/php -v"

As you can see, we declare the PHP version, the extension and the SAPI (CLI, FPM, etc), that we want “baked-in” to the final binary – in this example I only used “filter” as I want to run a minimal PHP CLI for use with a tiny PHP SLIM application.

The resulting binary from my example will be 4MB in size. Instead of starting PHPs built-in server with php -S localhost:<port> you would use the binary in your project:

bin/php -q -S localhost:8008 -t public

To get started with static-php-cli I would recommend to install it globally:

composer global require crazywhalecc/static-php-cli
spc --help