Skip to content

Commit c95b579

Browse files
committed
[PHP] Use a contextual SAPI name
1 parent 94fd429 commit c95b579

File tree

13 files changed

+192
-35
lines changed

13 files changed

+192
-35
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
dnl config.m4 for extension wasm_sapi_override
2+
3+
PHP_ARG_ENABLE(wasm_sapi_override, whether to enable wasm_sapi_override support,
4+
[ --enable-wasm_sapi_override Enable wasm_sapi_override support])
5+
6+
if test "$PHP_wasm_sapi_override" != "no"; then
7+
PHP_NEW_EXTENSION(wasm_sapi_override, wasm_sapi_override.c, $ext_shared)
8+
fi
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ARG_ENABLE("wasm_sapi_override", "Enable wasm_sapi_override support", "no");
2+
3+
if (PHP_wasm_sapi_override == "yes") {
4+
EXTENSION("wasm_sapi_override", "wasm_sapi_override.c");
5+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* Allows changing the SAPI name at runtime.
3+
*
4+
* Affects:
5+
* - php_sapi_name()
6+
* - PHP_SAPI constant
7+
*
8+
* Usage:
9+
* ```php
10+
* set_sapi_name('wasm');
11+
* ```
12+
*/
13+
#ifdef HAVE_CONFIG_H
14+
# include "config.h"
15+
#endif
16+
17+
#include "php.h"
18+
#include "SAPI.h"
19+
#include "zend_constants.h"
20+
#include "ext/standard/info.h"
21+
#include "wasm_sapi_override.h"
22+
23+
static char *set_sapi_name_original_name = NULL;
24+
static char *set_sapi_name_prev_allocated = NULL;
25+
26+
/* {{{ proto bool set_sapi_name(string $new_name) */
27+
PHP_FUNCTION(set_sapi_name)
28+
{
29+
char *new_name;
30+
size_t new_len;
31+
32+
ZEND_PARSE_PARAMETERS_START(1, 1)
33+
Z_PARAM_STRING(new_name, new_len)
34+
ZEND_PARSE_PARAMETERS_END();
35+
36+
/* --- overwrite sapi_module.name ------------------------------------- */
37+
if (!set_sapi_name_original_name) {
38+
set_sapi_name_original_name = sapi_module.name; /* remember for restore */
39+
}
40+
41+
char *buf = pemalloc(new_len + 1, 1); /* persistent */
42+
memcpy(buf, new_name, new_len);
43+
buf[new_len] = '\0';
44+
45+
if (set_sapi_name_prev_allocated) {
46+
pefree(set_sapi_name_prev_allocated, 1);
47+
}
48+
sapi_module.name = buf;
49+
set_sapi_name_prev_allocated = buf;
50+
51+
/* --- update PHP_SAPI constant --------------------------------------- */
52+
zend_string *const_name = zend_string_init("PHP_SAPI", sizeof("PHP_SAPI") - 1, 0);
53+
zend_constant *c = zend_hash_find_ptr(EG(zend_constants), const_name);
54+
55+
zend_string *new_zstr = zend_string_init(new_name, new_len, 1); /* persistent */
56+
57+
if (c) {
58+
if (Z_TYPE(c->value) == IS_STRING) {
59+
zend_string *old = Z_STR(c->value);
60+
/* Only release if it's not interned (interned strings live for entire process) */
61+
if (!ZSTR_IS_INTERNED(old)) {
62+
zend_string_release_ex(old, 1);
63+
}
64+
}
65+
ZVAL_STR(&c->value, new_zstr);
66+
} else {
67+
zval zv;
68+
ZVAL_STR(&zv, new_zstr);
69+
zend_register_constant(&(zend_constant){
70+
.name = const_name,
71+
.value = zv,
72+
});
73+
}
74+
zend_string_release_ex(const_name, 0);
75+
76+
RETURN_TRUE;
77+
}
78+
/* }}} */
79+
80+
/* arginfo */
81+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_sapi_name, 0, 1, _IS_BOOL, 0)
82+
ZEND_ARG_TYPE_INFO(0, new_name, IS_STRING, 0)
83+
ZEND_END_ARG_INFO()
84+
85+
/* function list */
86+
static const zend_function_entry wasm_sapi_override_functions[] = {
87+
PHP_FE(set_sapi_name, arginfo_set_sapi_name)
88+
PHP_FE_END
89+
};
90+
91+
/* MINIT / MSHUTDOWN */
92+
static PHP_MINIT_FUNCTION(wasm_sapi_override) { return SUCCESS; }
93+
94+
static PHP_MSHUTDOWN_FUNCTION(wasm_sapi_override)
95+
{
96+
if (set_sapi_name_prev_allocated) {
97+
pefree(set_sapi_name_prev_allocated, 1);
98+
set_sapi_name_prev_allocated = NULL;
99+
}
100+
if (set_sapi_name_original_name) {
101+
sapi_module.name = set_sapi_name_original_name;
102+
}
103+
return SUCCESS;
104+
}
105+
106+
/* info */
107+
static PHP_MINFO_FUNCTION(wasm_sapi_override)
108+
{
109+
php_info_print_table_start();
110+
php_info_print_table_row(2, "set_sapi_name support", "enabled");
111+
php_info_print_table_row(2, "version", PHP_WASM_SAPI_OVERRIDE_MODULE_VERSION);
112+
php_info_print_table_row(2, "current SAPI", sapi_module.name);
113+
php_info_print_table_end();
114+
}
115+
116+
/* module entry */
117+
zend_module_entry wasm_sapi_override_module_entry = {
118+
STANDARD_MODULE_HEADER,
119+
"wasm_sapi_override", /* Extension name */
120+
wasm_sapi_override_functions, /* Function entries */
121+
PHP_MINIT(wasm_sapi_override), /* PHP_MINIT - Module initialization */
122+
PHP_MSHUTDOWN(wasm_sapi_override), /* PHP_MSHUTDOWN - Module shutdown */
123+
NULL, /* PHP_RINIT - Request initialization */
124+
NULL, /* PHP_RSHUTDOWN - Request shutdown */
125+
PHP_MINFO(wasm_sapi_override), /* PHP_MINFO - Module info */
126+
PHP_WASM_SAPI_OVERRIDE_MODULE_VERSION, /* Version */
127+
STANDARD_MODULE_PROPERTIES
128+
};
129+
130+
#ifdef COMPILE_DL_WASM_SAPI_OVERRIDE
131+
# ifdef ZTS
132+
ZEND_TSRMLS_CACHE_DEFINE()
133+
# endif
134+
ZEND_GET_MODULE(wasm_sapi_override)
135+
#endif
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/* wasm_memory_storage extension for PHP */
2+
3+
#ifndef PHP_WASM_SAPI_OVERRIDE_H
4+
# define PHP_WASM_SAPI_OVERRIDE_H
5+
6+
extern zend_module_entry wasm_sapi_override_module_entry;
7+
# define phpext_wasm_sapi_override_ptr &wasm_sapi_override_module_entry
8+
9+
# define PHP_WASM_SAPI_OVERRIDE_MODULE_VERSION "0.0.1"
10+
11+
#endif /* PHP_WASM_SAPI_OVERRIDE_H */

packages/php-wasm/compile/php/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ RUN git clone https://github.com/php/php-src.git php-src \
2323
# Work around memory leak due to PHP using Emscripten's incomplete mmap/munmap support
2424
COPY ./php-wasm-memory-storage /root/php-src/ext/wasm_memory_storage
2525

26+
# Allow changing the SAPI name at runtime
27+
COPY ./php-wasm-sapi-override /root/php-src/ext/wasm_sapi_override
28+
2629
# Polyfill for dns functions
2730
COPY ./php-wasm-dns-polyfill/ /root/php-src/ext/dns_polyfill
2831

@@ -412,6 +415,7 @@ CURL_LIBS="-I/root/lib/lib -L/root/lib/lib" \
412415
--enable-ctype \
413416
--enable-tokenizer \
414417
--enable-wasm_memory_storage \
418+
--enable-wasm_sapi_override \
415419
--enable-dns_polyfill \
416420
--enable-post_message_to_js \
417421
$(cat /root/.php-configure-flags)

packages/php-wasm/supported-php-versions.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* @property {string} lastRelease
77
*/
88

9-
export const lastRefreshed = '2025-07-22T07:55:56.719Z';
9+
export const lastRefreshed = '2025-07-24T23:47:22.787Z';
1010

1111
/**
1212
* @type {PhpVersion[]}

packages/php-wasm/universal/src/lib/php.ts

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ type MountObject = {
6868
*/
6969
export class PHP implements Disposable {
7070
protected [__private__dont__use]: any;
71-
#sapiName?: string;
7271
#webSapiInitialized = false;
7372
#wasmErrorsTarget: UnhandledRejectionsTarget | null = null;
7473
#eventListeners: Map<string, Set<PHPEventListener>> = new Map();
@@ -77,6 +76,7 @@ export class PHP implements Disposable {
7776
requestHandler?: PHPRequestHandler;
7877
private cliCalled = false;
7978
private runStreamCalled = false;
79+
private sapiName = 'playground';
8080

8181
/**
8282
* An exclusive lock that prevent multiple requests from running at
@@ -338,19 +338,8 @@ export class PHP implements Disposable {
338338

339339
/** @inheritDoc */
340340
async setSapiName(newName: string) {
341-
const result = this[__private__dont__use].ccall(
342-
'wasm_set_sapi_name',
343-
NUMBER,
344-
[STRING],
345-
[newName]
346-
);
347-
if (result !== 0) {
348-
throw new Error(
349-
'Could not set SAPI name. This can only be done before the PHP WASM module is initialized.' +
350-
'Did you already dispatch any requests?'
351-
);
352-
}
353-
this.#sapiName = newName;
341+
this.sapiName = newName;
342+
this.defineConstant('PLAYGROUND_SAPI_NAME', newName);
354343
}
355344

356345
/**
@@ -591,6 +580,7 @@ export class PHP implements Disposable {
591580
async () => {
592581
if (!this.#webSapiInitialized) {
593582
await this.#initWebRuntime();
583+
this.setSapiName(this.sapiName);
594584
this.#webSapiInitialized = true;
595585
}
596586
if (
@@ -1252,10 +1242,6 @@ export class PHP implements Disposable {
12521242
// Initialize the new runtime
12531243
this.initializeRuntime(runtime);
12541244

1255-
if (this.#sapiName) {
1256-
this.setSapiName(this.#sapiName);
1257-
}
1258-
12591245
// Copy the old /internal directory to the new filesystem
12601246
copyFS(oldFS, this[__private__dont__use].FS, '/internal');
12611247

@@ -1346,14 +1332,20 @@ export class PHP implements Disposable {
13461332
);
13471333
}
13481334

1335+
const sapiNameBefore = this.sapiName;
1336+
this.sapiName = 'cli';
13491337
return await this.#executeWithErrorHandling(() => {
13501338
return this[__private__dont__use].ccall('run_cli', null, [], [], {
13511339
async: true,
13521340
});
1353-
}).then((response) => {
1354-
response.exitCode.finally(release);
1355-
return response;
1356-
});
1341+
})
1342+
.then((response) => {
1343+
response.exitCode.finally(release);
1344+
return response;
1345+
})
1346+
.finally(() => {
1347+
this.setSapiName(sapiNameBefore);
1348+
});
13571349
}
13581350

13591351
setSkipShebang(shouldSkip: boolean) {
Binary file not shown.

packages/php-wasm/web/public/php/jspi/php_8_0.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/playground/client/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export async function startPlaygroundWeb({
176176
* @see https://github.com/WordPress/wordpress-playground/pull/2295
177177
*/
178178
if (compiled.features.networking) {
179-
await playground.prefetchUpdateChecks();
179+
// await playground.prefetchUpdateChecks();
180180
}
181181
progressTracker.finish();
182182

0 commit comments

Comments
 (0)