Skip to content

Commit 82f7a0e

Browse files
feat: hide background-worker functions in CLI mode
In CLI mode (frankenphp php-cli), there is no worker pool and no SAPI request cycle; the background-worker API is meaningless. Rather than throwing at call time, the four functions are now unregistered from the function table in MINIT when the SAPI is "cli": frankenphp_require_background_worker frankenphp_set_vars frankenphp_get_vars frankenphp_get_worker_handle This lets library code detect the mode cleanly via function_exists() and fall back to alternative config sources without try/catch. HTTP-mode set_vars cannot be hidden the same way because the context (HTTP worker vs background worker) is per-thread, not per-process; threads share the PHP module and function table. Runtime throw stays the approach there, matching PHP's convention for pcntl_*/posix_*.
1 parent 963349b commit 82f7a0e

4 files changed

Lines changed: 52 additions & 0 deletions

File tree

cli_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ func TestExecuteScriptCLI(t *testing.T) {
3232
assert.Contains(t, stdoutStderrStr, "From the CLI")
3333
}
3434

35+
func TestBackgroundWorkerFunctionsHiddenInCLI(t *testing.T) {
36+
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
37+
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")
38+
}
39+
40+
cmd := exec.Command("internal/testcli/testcli", "testdata/bg-worker-cli-hidden.php")
41+
stdoutStderr, err := cmd.CombinedOutput()
42+
assert.NoError(t, err)
43+
44+
out := string(stdoutStderr)
45+
assert.Contains(t, out, "frankenphp_require_background_worker:hidden")
46+
assert.Contains(t, out, "frankenphp_set_vars:hidden")
47+
assert.Contains(t, out, "frankenphp_get_vars:hidden")
48+
assert.Contains(t, out, "frankenphp_get_worker_handle:hidden")
49+
// unrelated functions must remain available
50+
assert.Contains(t, out, "frankenphp_log:exists")
51+
}
52+
3553
func TestExecuteCLICode(t *testing.T) {
3654
if _, err := os.Stat("internal/testcli/testcli"); err != nil {
3755
t.Skip("internal/testcli/testcli has not been compiled, run `cd internal/testcli/ && go build`")

docs/background-workers.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ $config = frankenphp_get_vars('config-watcher');
222222

223223
### Graceful Degradation
224224

225+
The `frankenphp_require_background_worker`, `frankenphp_set_vars`, `frankenphp_get_vars`, and `frankenphp_get_worker_handle` functions are only exposed when FrankenPHP runs in SAPI mode (HTTP server). In CLI mode (`frankenphp php-cli ...`), they are hidden, so `function_exists()` returns `false`. Library code can rely on this to degrade gracefully:
226+
225227
```php
226228
if (function_exists('frankenphp_get_vars')) {
227229
frankenphp_require_background_worker('config-watcher');

frankenphp.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,18 @@ PHP_MINIT_FUNCTION(frankenphp) {
12361236
pthread_atfork(NULL, NULL, frankenphp_fork_child);
12371237
#endif
12381238

1239+
/* Background workers do not exist in CLI mode (no worker pool, no SAPI
1240+
* request cycle). Hide the related functions so function_exists() reports
1241+
* false and library code can detect this cleanly. */
1242+
if (sapi_module.name && strcmp(sapi_module.name, "cli") == 0) {
1243+
static const char *bg_functions[] = {
1244+
"frankenphp_require_background_worker", "frankenphp_set_vars",
1245+
"frankenphp_get_vars", "frankenphp_get_worker_handle", NULL};
1246+
for (const char **f = bg_functions; *f != NULL; f++) {
1247+
zend_hash_str_del(CG(function_table), *f, strlen(*f));
1248+
}
1249+
}
1250+
12391251
zend_function *func;
12401252

12411253
// Override putenv

testdata/bg-worker-cli-hidden.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
// In CLI mode, background-worker functions are not exposed at all
4+
// (function_exists() returns false).
5+
6+
$functions = [
7+
'frankenphp_require_background_worker',
8+
'frankenphp_set_vars',
9+
'frankenphp_get_vars',
10+
'frankenphp_get_worker_handle',
11+
];
12+
13+
foreach ($functions as $fn) {
14+
echo $fn . ':' . (function_exists($fn) ? 'exists' : 'hidden') . "\n";
15+
}
16+
17+
// Other frankenphp functions should still exist
18+
echo 'frankenphp_log:' . (function_exists('frankenphp_log') ? 'exists' : 'hidden') . "\n";
19+
20+
exit(0);

0 commit comments

Comments
 (0)