[php-src] Issue #22133: API to obtain the compiled-in default value of a PHP INI setting

[email protected] (sebastianbergmann) Sat, 23 May 2026 04:53:49 +0000
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/22133
Author: sebastianbergmann

### Description

Provide an API to obtain the compiled-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 compiled-in defaults is to spawn a child process with all configuration disabled and serialize its INI table:

```php
$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 <https://github.com/sebastianbergmann/environment/issues/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:

```php
/**
 * 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:

```php
$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.