Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions