diff --git a/evals/evals.json b/evals/evals.json index 24c0dd4..d94ab55 100644 --- a/evals/evals.json +++ b/evals/evals.json @@ -455,5 +455,26 @@ "description": "Recommends matching CI via the project's runner / Docker (e.g. make code-style or docker run php:8.2-cli)" } ] + }, + { + "name": "php-subprocess-from-web-sapi", + "prompt": "This PHP service spawns a subprocess to run a CLI script. It works from our console command and cron, but crashes with \"ValueError: First element must contain a non-empty program name\" when the same code runs behind Apache mod_php. Fix it:\n\n```php\nmustRun();\n }\n}\n```", + "assertions": [ + { + "type": "content_contains", + "value": "PhpExecutableFinder", + "description": "Resolves the PHP binary via PhpExecutableFinder instead of \\PHP_BINARY" + }, + { + "type": "content_contains", + "value": "find(false)", + "description": "Uses find(false) so console/cron behaviour is unchanged" + }, + { + "type": "content_regex", + "value": "SAPI|mod_php|web|empty", + "description": "Explains that \\PHP_BINARY is empty under a non-CLI SAPI" + } + ] } ] diff --git a/skills/php-modernization/references/symfony-patterns.md b/skills/php-modernization/references/symfony-patterns.md index 818e93e..6261a07 100644 --- a/skills/php-modernization/references/symfony-patterns.md +++ b/skills/php-modernization/references/symfony-patterns.md @@ -433,6 +433,49 @@ final class NotificationService } ``` +## Spawning a PHP subprocess (`Process` component) + +`\PHP_BINARY` is an **empty string under a non-CLI SAPI** (Apache `mod_php`, +PHP-FPM). It only resolves to the interpreter path under the CLI SAPI. So +`new Process([\PHP_BINARY, $script, ...])` works from a console command, a +cron job, or CI, but throws from any web entry point: + +``` +ValueError: First element must contain a non-empty program name + at Symfony\Component\Process\Process::__construct() +``` + +The trap is that the CLI paths — the ones you test — pass, while the web path +fails only in production. Resolve the executable with `PhpExecutableFinder` +instead of reading the constant directly: + +```php +find(false); + +if (false === $php) { + throw new \RuntimeException('Could not locate the PHP CLI binary.'); +} + +$args = [$script, '--shard', '1/4']; // whatever the script needs +$process = new Process([$php, ...$args]); +$process->mustRun(); +``` + +`find(false)` returns `\PHP_BINARY` under CLI, so console/cron/CI behaviour is +unchanged; it only differs where the constant would have been empty. Handle the +`false` return — a container without a CLI binary on `PATH` is a real +possibility. Never hardcode `/usr/bin/php`: the path varies across base images. + ## Security Configuration ```php