Skip to content

API to obtain the built-in default value of a PHP INI setting #22133

@sebastianbergmann

Description

@sebastianbergmann

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions