I just wanted PHP 8.2 (Composer had other ideas)

A horror story of setting a minimum supported php version

2026-04-215 minWesley Schwenglephpversioningminimum versioncomposerprojectdockereveninglaravelperl

TL;DR

This is a rant, a developer war story of how to use docker to work around a problem that shouldn’t exist in the first place. Configure your PHP project with a different, lower version, of PHP than you are using yourself. Read how I spent an evening fighting PHP.

The bare minimum

Can someone in PHP land please tell me why composer is unable to create a project on PHP version 8.2 when I’m running PHP version 8.4? I’m creating a PHP app that needs to run on bookworm, aka, Debian 12, which ships PHP 8.2.

I’ve been battling it out with PHP for an whole evening just to get rid of an error that is caused by vendor/composer/platform_check.php:

php
if (!(PHP_VERSION_ID >= 80400)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.4.0" .
You are running ' . PHP_VERSION . '.';
}

Damned if you do, damned if you don’t

There is a chicken and an egg. For those who don’t know PHP or composer: composer won’t let you create a project in a directory that isn’t empty. You can’t tell composer to use php 8.2 before you create a project. Telling composer to use php 8.2 in an empty directory is not possible: composer config php 8.2 fails because it cannot find composer.json. Adding it, manually, composer complains the directory isn’t empty. composer create-project doesn’t have a way to tell composer, use php version 8.2 for this.

In Perl, use v5.26 in a module. That’s it. On Perl 5.40 or 5.38 or 5.42. It just works. Easy. PHP? NOT SO MUCH.

The internet says you need to run the version of PHP you want as a minimum. Now, in what world does that make sense? The PHP world, it seems. I’m still crying a little.

Crush your soul

How can you solve it? Pfff, by selling your soul to the devil. I eventually gave in and went the docker route. Ok, docker ain’t the devil, I actually love the product but it is solving a problem we shouldn’t be solving with Docker in the first place. I take a base image, not shown here, and I use multistage builds to create a project initialization layer. I add zip and unzip to get rid of a warning by composer and I install the version of Laravel and Livewire I want:

Dockerfile
FROM base AS init
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /tmpapp
RUN <<OEF
apt-get update -y
apt-get install -y zip unzip
composer create-project laravel/laravel:^12.0 .
php -r '
$composer = json_decode(file_get_contents("composer.json"), true);
$composer["scripts"]["post-create-project-cmd"] = array_values(array_filter(
$composer["scripts"]["post-create-project-cmd"],
fn($cmd) => !str_contains($cmd, "database/database.sqlite")
));
file_put_contents("composer.json", json_encode($composer, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES));
'
composer require livewire/livewire=^3.7
OEF

Now you can create a PHP docker image that can create your minimum version project, drama free:

zsh
slug="some-name-you-decided-on"
docker build -f docker/php/Dockerfile . --target init --tag "$slug"
myid=$(docker create "$slug")
docker cp $myid:/tmpapp/. .
docker rm $myid

This is what you need to do in terms of soul selling to get a project of the ground. All of that, to get to this:

php
if (!(PHP_VERSION_ID >= 80200)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.2.0" .
You are running ' . PHP_VERSION . '.';
}

And before you tell me, but that is easily doable by a small diff. Yes, that could be. The other dependencies you need to fix? Not so easy. Because laravel/framework bumps to 12.56.x on PHP 8.4 and that, haha, installs a dependency that only works on php 8.4.
I dunno if PHP does semver, but if they do they should consider abandoning it, because if an dependency breaks a downstream consumer, us, the 8.2 project, it should be 13.x.y. Fixing your dependency tree after the fact is… argh! The more I play with other languages besides Perl, the more I love Perl. The write once language, at least we have versioning fixed.

Time to start your new project and fire that baby up!

Spoiler: it doesn’t.

This is what we call “from rain into the drip” in Dutch, aka from one problem into the next. When you start your project, you get hit by tempnam(): file created in the system's temporary directory errors from Laravel. It tries to cache some files and it can’t. You can use tinker to find out what is what:

sh
$ php artisan tinker
# do something like this:
app('files')->directories(storage_path('framework/cache'))
app('files')->directories(storage_path('framework'))
# or this
collect(['storage/framework/sessions', 'storage/framework/views',
'storage/framework/cache', 'storage/logs', 'bootstrap/cache'])
->map(fn($p) => [$p => is_writable(base_path($p))])->dd()

The permission slip

In the past I’ve set some permissions to directories, but you use the same pattern and suddenly something breaks somewhere. And other times it don’t. I solve this problem, forget to document it — because frustration and I just wanted to move on. Yes, the header was a pun.
This evening was different. Permanent solution, document it for future self and projects. You solve this by doing two things:

  1. Set the COMPOSER_CACHE_DIR to something writable, e.g., /tmp/composer/cache
  2. You use a volume dedicated for the frameworks/views directory.
yaml
---
services:
php:
user: www
environment:
COMPOSER_CACHE_DIR: /tmp/composer/cache
volumes:
- ./:/var/www:rw
- viewcache:/var/www/storage/framework/views
volumes:
viewcache:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "uid=1000,gid=1000"

Now you can start up your PHP Laravel app in Docker without any problem. That saves you another half of an evening. That I lost trying to simplify my PHP stack.