Look, we need to talk about your PHP addiction. I get it – you’ve mastered the server-side arts, bent databases to your will, and perhaps even dabbled in our previous dark art of threading. But something’s missing, isn’t it? You look at those fancy JavaScript developers with their client-side rendering and think “why not PHP?”
Well, my dedicated PHP acolyte, today we’re going to commit what many would consider a cardinal sin: We’re going to run PHP in the browser. Through the forbidden magic of WebAssembly, we’ll make the impossible possible. But first…
⚠️ STANDARD DARK ARTS DISCLAIMER ⚠️
This article is for entertainment and educational purposes only. If you deploy this in production, you will be visited by three ghosts: the Ghost of Bad Architecture Past, the Ghost of Debugging Present, and the Ghost of Maintenance Future. You have been warned.
The Theory of Forbidden Knowledge
Before we dive into our dark ritual, let’s understand what we’re attempting here. WebAssembly (WASM) is a binary instruction format that allows us to run code written in languages like C++ and Rust in the browser at near-native speed. PHP itself is written in C, which means…yes, with the right incantations, we can compile PHP itself to WASM.
The Components of Our Curse
- Emscripten – Our magical compiler that will transform PHP into WebAssembly
- PHP source code – The victim of our transformation
- JavaScript glue code – The unholy binding that will tie it all together
- A very specific set of PHP extensions – Because we’re masochists, but not completely insane
Setting Up the Dark Ritual
First, you’ll need to install Emscripten. If you’re on a Unix-like system:
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
Next, we need to get PHP’s source code and prepare it for our dark purposes:
git clone https://github.com/php/php-src.git
cd php-src
git checkout PHP-8.2.0 # Let's use a stable version for our unstable purposes
The Cursed Configuration
Here’s where it gets interesting. We need to configure PHP to be compiled with Emscripten. Create a build script named build-wasm.sh
:
#!/bin/bash
emconfigure ./configure \
--disable-all \
--disable-cgi \
--disable-cli \
--disable-rpath \
--disable-phpdbg \
--disable-shared \
--enable-static \
--without-pear \
--without-pcre-jit \
--with-layout=GNU \
--enable-embed=static \
CFLAGS="-O3" \
LDFLAGS="-O3"
emmake make
The Forbidden Function
Now for the fun part. We’ll create a simple PHP function that we want to run in the browser. Create a file called dark-arts.php
:
<?php
function summonDarkPower($input) {
return "🦇 Behold, client-side PHP has processed: " . $input . " 🦇";
}
?>
The Binding Spell (JavaScript)
We need some JavaScript to bind our PHP WASM module to the browser world. Create dark-binding.js
:
let phpModule;
async function initPHP() {
phpModule = await Module();
// Initialize PHP WASM environment
phpModule.ccall('php_embed_init', 'number', ['number', 'number'], [0, 0]);
}
async function runPHP(input) {
if (!phpModule) await initPHP();
// Allocate memory for our input string
const inputPtr = phpModule.allocate(
phpModule.intArrayFromString(input),
phpModule.ALLOC_NORMAL
);
// Create our PHP code string
const phpCode = `<?php
include 'dark-arts.php';
echo summonDarkPower('${input}');
?>`;
const codePtr = phpModule.allocate(
phpModule.intArrayFromString(phpCode),
phpModule.ALLOC_NORMAL
);
// Execute PHP code
const result = phpModule.ccall(
'zend_eval_string',
'number',
['string', 'string', 'string'],
[phpCode, 'dark-arts', '1']
);
// Clean up
phpModule._free(inputPtr);
phpModule._free(codePtr);
return result;
}
The Final Incantation
Now, let’s put it all together in an HTML file that summons our dark creation:
<!DOCTYPE html>
<html>
<head>
<title>PHP Dark Arts: Client-Side Edition</title>
<style>
body {
background: #1a1a1a;
color: #00ff00;
font-family: monospace;
}
#output {
border: 1px solid #00ff00;
padding: 20px;
margin: 20px;
min-height: 100px;
}
</style>
</head>
<body>
<h1>🕯️ PHP Dark Arts Console 🕯️</h1>
<input type="text" id="input" placeholder="Enter text to process...">
<button onclick="performRitual()">Summon</button>
<div id="output"></div>
<script src="php-wasm.js"></script>
<script src="dark-binding.js"></script>
<script>
async function performRitual() {
const input = document.getElementById('input').value;
const output = document.getElementById('output');
try {
const result = await runPHP(input);
output.innerHTML += `<div>${result}</div>`;
} catch (e) {
output.innerHTML += `<div style="color: red">The ritual failed: ${e}</div>`;
}
}
</script>
</body>
</html>
Running Our Creation
To witness this abomination in action:
- Compile everything with Emscripten:
emcc -o php-wasm.js php-src/sapi/embed/php_embed.c \
php-src/.libs/libphp.a \
-s WASM=1 \
-s EXPORTED_FUNCTIONS='["_php_embed_init", "_zend_eval_string"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall", "allocate", "intArrayFromString", "ALLOC_NORMAL"]' \
-s INITIAL_MEMORY=33554432
- Serve it with your favorite local web server:
php -S localhost:8080
- Open your browser and behold your creation at
http://localhost:8080
The Results
If everything went according to plan (ha!), you should now have a working example of PHP running in your browser. Type something into the input box, click “Summon,” and watch as your text is processed by actual PHP code running in WebAssembly.
But Why Though?
Look, we’ve done something impressive here. We’ve taken PHP, a language designed to generate HTML on the server, and forced it to run in the browser through sheer willpower and questionable decision-making. Is it practical? Absolutely not. Is it cool? Debatable. Will it make senior developers cry? Absolutely.
Potential “Uses” (Air Quotes Heavily Implied)
- Running legacy PHP code in the browser when migrating to a modern frontend framework
- Making your website completely incomprehensible to future maintainers
- Winning bets about what’s possible with PHP
- Getting fired in a particularly memorable way
Next Time…
In our next installment of PHP Dark Arts, we’ll explore creating a blockchain implementation entirely in PHP using nothing but arrays and regret. Until then, keep your code dark and your intentions questionable!