Description
Provide an API to obtain the built-in default value of a PHP INI setting (the value that would be in effect if no php.ini, no additional scanned INI files, and no -d/ini_set() overrides were applied) from within a running PHP process, without having to spawn a child process.
Motivation
A program sometimes needs to know which INI settings differ from the value a fresh PHP process would use, for example to faithfully reproduce the current configuration in a sub-process via -d key=value. PHPUnit relies on this when running tests in separate processes, and the sebastianbergmann/environment component provides Runtime::getCurrentSettings() for exactly this purpose.
The information PHP exposes today is not sufficient to do this reliably:
ini_get($name) returns the current value (after php.ini, scanned INI files, -d, and ini_set()).
ini_get_all($extension, true) additionally exposes global_value (the
"master" value) and local_value. However, global_value already reflects values set in php.ini, in additionally scanned INI files, and via -d. It is therefore not the compiled-in default.
get_cfg_var() reads from the INI files as well.
There is no API that returns the value a setting would have with no configuration applied, i.e. the value built into the engine/extension via PHP_INI_ENTRY/STD_PHP_INI_ENTRY (the registered default) or, for settings registered with OnModify semantics, the module's default.
Current workaround and why it is problematic
The only known way to obtain the built-in defaults is to spawn a child process with all configuration disabled and serialize its INI table:
$process = proc_open(
[PHP_BINARY, '-n', '-r', 'echo json_encode(ini_get_all(null, true));'],
[1 => ['pipe', 'w']],
$pipes,
);
$stdout = stream_get_contents($pipes[1]);
proc_close($process);
$defaults = json_decode($stdout, true);
This works, but spawning a child process has a serious side effect: proc_open() (like popen(), exec(), shell_exec(), system()) forks the current process. Extensions that register pthread_atfork() handlers run those handlers on every fork. The gRPC extension, for instance, reacts to the fork by attempting to re-initialize its event engine and writes diagnostics to STDERR:
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
E0000 00:00:1779463505.812250 1517 ev_posix.cc:132] No event engine could be initialized from ...
This output is emitted in the parent process (and/or in the forked child before execve() replaces the image), so it cannot be suppressed by redirecting the child's file descriptors. In a test runner that is configured to be strict about output it fails the entire run. See sebastianbergmann/environment#97.
In short: a purely informational query ("what is the default value of this setting?") currently requires forking the process, and forking has process-global, extension-dependent side effects that the caller cannot control or avoid.
Proposed API
A function that returns the compiled-in default for one or all settings without spawning a process. For example:
/**
* Returns the compiled-in default value of $name, or null when the
* setting is unknown. The returned value ignores php.ini, additionally
* scanned INI files, -d command-line overrides, and ini_set() changes.
*/
function ini_get_default(string $name): ?string {}
or, mirroring ini_get_all(), an additional column on the existing structure:
$all = ini_get_all(null, true);
// $all['precision'] === [
// 'global_value' => '14',
// 'local_value' => '14',
// 'default_value' => '14', // <-- new: compiled-in default
// 'access' => 7,
// ];
The engine already knows these defaults: each INI entry is registered with its default string (PHP_INI_ENTRY3/STD_PHP_INI_ENTRY and friends), and that default is applied before configuration files are parsed. Exposing it is primarily a matter of recording the registered default alongside the current master/local values.
Alternatives considered
- Parsing the INI files in-process and comparing. This detects values set in
php.ini/scanned files, but cannot account for settings left at their compiled-in default, nor for -d overrides, which is precisely the gap that motivated the child-process approach.
- Suppressing the child's output. Not possible from PHP: the offending output is written by an extension's atfork handler to the process's real file descriptor 2 before any redirection set up for the child takes effect.
- Gating the child process on specific extensions (e.g. skipping it when
extension_loaded('grpc') is true). This would be fragile: it special-cases one extension, silently reduces fidelity, and does not cover other extensions that install fork-hostile handlers.
A first-class API would remove the need to fork at all for this use case and would make the result correct and side-effect-free for every extension.
Description
Provide an API to obtain the built-in default value of a PHP INI setting (the value that would be in effect if no
php.ini, no additional scanned INI files, and no-d/ini_set()overrides were applied) from within a running PHP process, without having to spawn a child process.Motivation
A program sometimes needs to know which INI settings differ from the value a fresh PHP process would use, for example to faithfully reproduce the current configuration in a sub-process via
-d key=value. PHPUnit relies on this when running tests in separate processes, and thesebastianbergmann/environmentcomponent providesRuntime::getCurrentSettings()for exactly this purpose.The information PHP exposes today is not sufficient to do this reliably:
ini_get($name)returns the current value (afterphp.ini, scanned INI files,-d, andini_set()).ini_get_all($extension, true)additionally exposesglobal_value(the"master" value) and
local_value. However,global_valuealready reflects values set inphp.ini, in additionally scanned INI files, and via-d. It is therefore not the compiled-in default.get_cfg_var()reads from the INI files as well.There is no API that returns the value a setting would have with no configuration applied, i.e. the value built into the engine/extension via
PHP_INI_ENTRY/STD_PHP_INI_ENTRY(the registered default) or, for settings registered withOnModifysemantics, the module's default.Current workaround and why it is problematic
The only known way to obtain the built-in defaults is to spawn a child process with all configuration disabled and serialize its INI table:
This works, but spawning a child process has a serious side effect:
proc_open()(likepopen(),exec(),shell_exec(),system()) forks the current process. Extensions that registerpthread_atfork()handlers run those handlers on every fork. The gRPC extension, for instance, reacts to the fork by attempting to re-initialize its event engine and writes diagnostics toSTDERR:This output is emitted in the parent process (and/or in the forked child before
execve()replaces the image), so it cannot be suppressed by redirecting the child's file descriptors. In a test runner that is configured to be strict about output it fails the entire run. See sebastianbergmann/environment#97.In short: a purely informational query ("what is the default value of this setting?") currently requires forking the process, and forking has process-global, extension-dependent side effects that the caller cannot control or avoid.
Proposed API
A function that returns the compiled-in default for one or all settings without spawning a process. For example:
or, mirroring
ini_get_all(), an additional column on the existing structure:The engine already knows these defaults: each INI entry is registered with its default string (
PHP_INI_ENTRY3/STD_PHP_INI_ENTRYand friends), and that default is applied before configuration files are parsed. Exposing it is primarily a matter of recording the registered default alongside the current master/local values.Alternatives considered
php.ini/scanned files, but cannot account for settings left at their compiled-in default, nor for-doverrides, which is precisely the gap that motivated the child-process approach.extension_loaded('grpc')is true). This would be fragile: it special-cases one extension, silently reduces fidelity, and does not cover other extensions that install fork-hostile handlers.A first-class API would remove the need to fork at all for this use case and would make the result correct and side-effect-free for every extension.