Why it's even possible comes down to one thing this audience will care about: PSRAM. The engine allocates megabytes of heap, which doesn't fit in a few hundred KB of internal SRAM. The P4-Pico's 32 MB of in-package PSRAM is what makes it fit. Everything else followed from that.
The board is a Waveshare ESP32-P4-Pico (dual-core RISC-V, running at 360 MHz here, 32 MB PSRAM, 32 MB flash, microSD over SDIO). Rigorous engineering rationale for the board choice: I had it lying around.
The interesting part wasn't compiling PHP, it was convincing an engine full of OS assumptions that it's at home on a chip with no OS:
- No ./configure (its test programs are RISC-V binaries that die on the build host), so php_config.h is hand-written and every POSIX symbol PHP expects but newlib doesn't have is stubbed out.
- Zend's memory manager thinks in 2 MB blocks, so it's switched off (USE_ZEND_ALLOC=0) and every malloc is routed to PSRAM.
- The fast VM (computed-goto + global registers) doesn't compile here, so it uses the portable "call" VM. A touch slower, works everywhere.
- A few surprises that only showed up with the chip on the bench, like the microSD needing its own internal LDO channel powered before it would respond.
Code: Select all
<?php
define('LED', 2);
function setup(): void {
gpio_mode(LED, GPIO_OUTPUT);
echo "PHP " . PHP_VERSION . " on ESP32-P4\n";
}
function loop(int $tick): void {
gpio_write(LED, $tick % 2);
delay(500); // vTaskDelay under the hood, so the watchdog stays happy
}

It goes further than blinking a LED. Composer autoloading works with a real vendor/ tree loaded from the card, and SQLite is in: PDO_SQLite with SQLite's own source compiled in, so you get a real SQL database living on the microSD, queries and transactions and all. On top of that, Eloquent (yes, Laravel's ORM) runs against it. A full ORM querying an SQLite database, on a microcontroller. I have no idea why either, but it's genuinely usable: local persistent storage that survives a reboot, which on an edge device is the one thing here that's almost justifiable.
If you want to try it without touching ESP-IDF by hand, there's now a companion CLI, phpflash, a single static Go binary:
Code: Select all
phpflash system-setup # installs ESP-IDF + the firmware repo
phpflash init my-project # scaffolds a project (board, extensions, starter sketch)
phpflash build
phpflash flash
phpflash monitor
Which ESP32s work: the deciding factor isn't the core (it's portable C on the "call" VM, so Xtensa and RISC-V both compile), it's PSRAM plus roomy flash (the image is ~3 MB, so 8 MB+). P4 is the target; ESP32-S3 is the most accessible way to reproduce it (Xtensa, up to 8-16 MB PSRAM, just needs recompiling for the target). WROVER-class ESP32/S2 are borderline. The C and H series have no PSRAM, so they're out regardless of speed.
Honest limits: no networking on this variant (yes, the web's language on a board that doesn't know what a network is; the irony isn't lost on me), so an HTTP/web-server mode is something I'm working on but it isn't there yet. No Fibers either (they'd need 32-bit RISC-V context-switch assembly that doesn't exist), and ext/date is an optional build (~650 KB) rather than on by default.
Firmware + porting notes + the "which chips work" table: https://github.com/php-baremetal/php-esp32
The flash tool: https://github.com/php-baremetal/flash-tool
Next steps:
esp32-p4-eth compatibility for web-server
esp32-s3 family
Happy to go into the memory routing, the newlib stubs, the SQLite build, or the board bring-up if anyone's curious.