pilotphp/config

Deterministic compiled configuration for the PilotPHP Agent-First framework.

Maintainers

Package info

gitlab.com/pilotphp/config

Issues

pkg:composer/pilotphp/config

Transparency log

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

0.0.1 2026-07-27 03:04 UTC

This package is not auto-updated.

Last update: 2026-07-28 00:10:33 UTC


README

Deterministic compiled configuration for the PilotPHP Agent-First framework.

Configuration is assembled and compiled before requests are served. In the worker, reading a value is one hash lookup — no file access, no environment access, no merging.

Installation

composer require pilotphp/config

Requires PHP 8.5. Depends on nothing but PHP and pilotphp/contracts.

What it does

  • Loads configuration from trusted PHP files, directories, and arrays
  • Merges ordered layers deterministically, refusing ambiguity
  • Keeps external values (secrets, environment) as unresolved references
  • Compiles to a byte-reproducible PHP artifact with a SHA-256 fingerprint
  • Serves an immutable, typed ConfigInterface at runtime

What it does not do

No DI container, kernel, runtime loop, RoadRunner, gRPC, HTTP, ORM, Blueprint, console commands, .env parsing, YAML/XML, secret-provider implementations, or Composer package scanning. Each belongs to its own package — see docs/architecture.md.

Development usage

use PilotPHP\Config\Build\{ConfigBuilder, ConfigLayer, ConfigLayerKind};
use PilotPHP\Config\Runtime\ImmutableConfig;
use PilotPHP\Config\Source\PhpFileConfigSource;
use PilotPHP\Config\Value\{ConfigValueResolver, EnvironmentValueResolver};

$result = new ConfigBuilder()
    ->addLayer(new ConfigLayer(
        id: 'framework',
        kind: ConfigLayerKind::FrameworkDefaults,
        data: ['app' => ['name' => 'unnamed', 'workers' => 1]],
    ))
    ->addSource(new PhpFileConfigSource(
        path: __DIR__ . '/config/app.php',
        id: 'application',
        kind: ConfigLayerKind::Application,
    ))
    ->build();

$config = new ImmutableConfig(
    new ConfigValueResolver([new EnvironmentValueResolver($environment)])->resolve($result->data()),
);

$config->getString('app.name');
$config->getInt('app.workers');

Production pipeline

Build and compile once, at deploy time:

use PilotPHP\Config\Compiler\PhpConfigCompiler;

new PhpConfigCompiler()->compile($result, __DIR__ . '/var/cache/config.php');

Worker boot

Then every worker boots from that one file:

use PilotPHP\Config\Compiler\CompiledConfigLoader;
use PilotPHP\Config\Runtime\ImmutableConfig;
use PilotPHP\Config\Value\{ConfigValueResolver, EnvironmentValueResolver};

$payload = new CompiledConfigLoader()->load(__DIR__ . '/var/cache/config.php');

$resolved = new ConfigValueResolver([
    new EnvironmentValueResolver($environment),
])->resolve($payload->data());

$config = new ImmutableConfig($resolved);

Three steps, once per process. Afterwards $config never touches the disk again. Both pipelines produce identical configuration — the integration suite asserts it.

Layers

KindValuePurpose
FrameworkDefaults100framework baseline
PackageDefaults200installed packages' defaults
Application300the application's own config
Environment400per-deployment overrides
Runtime500programmatic overrides, applied last

Sorted by kind, then order, then layer id — a total ordering, so the result never depends on the order sources were added.

Merging

Maps merge recursively; lists are replaced wholesale; a scalar replaces a scalar of the same type. Anything ambiguous is refused and must be stated explicitly:

use PilotPHP\Config\Merge\ConfigMerge;

return [
    'servers'  => ConfigMerge::append(['replica-3']),   // extend a list
    'features' => ConfigMerge::replace(['a' => true]),  // override any shape
    'legacy'   => ConfigMerge::remove(),                // delete a key
    'timeout'  => ConfigMerge::replace(1.5),            // int → float is a conflict
];

Removing a key that does not exist is an error, not a no-op — that is how a typo in an override file gets caught. Full table: docs/merge-semantics.md.

Environment references

use PilotPHP\Config\Value\{DeferredValue, DeferredValueType};

return [
    'database' => [
        'host'     => DeferredValue::environment('DB_HOST', default: 'localhost'),
        'port'     => DeferredValue::environment('DB_PORT', DeferredValueType::Integer, default: 5432),
        'password' => DeferredValue::environment('DB_PASSWORD', required: true),
    ],
];

These stay unresolved through merging, fingerprinting, and compilation, and are resolved once at boot. A secret therefore cannot reach the compiled artifact or the fingerprint.

required and default are mutually exclusive — the combination has no coherent meaning and is rejected at construction.

Casts are strict: DB_PORT=8O80 fails the boot loudly instead of quietly becoming 8. See docs/deferred-values.md.

Package defaults

A package declares where its defaults live; the application overrides them:

$registration->add(new ConfigDefaultsDeclaration(
    prefix: 'grpc',
    path: 'config/default.php',
));

See docs/package-integration.md.

Limitations

  • Configuration files are trusted code. They are required and may compute values. Loading configuration from an untrusted source is out of scope.
  • Side effects in configuration files are unsupported; a file is executed once per build, at an unspecified time.
  • Config map keys may not contain a dot (dots separate path segments) and may not start with __pilot_ (reserved).
  • [] is a list as far as PHP is concerned, so an empty array merging into a map is a shape conflict. Use ConfigMerge::replace().
  • The flat runtime index trades memory for O(1) reads; see docs/performance.md.

Development

make install
make check      # validate + cs-check + analyse + test
make benchmark  # measurement only, never a blocking test

Documentation

DocumentContents
architecture.mdPipeline, boundaries, trust model
merge-semantics.mdComplete merge table
deferred-values.mdProviders and secret safety
compilation.mdDeterministic artifacts
package-integration.mdPackage defaults
performance.mdHot-path policy and measurements
AGENTS.mdRules for changing this package

Repository

A standalone Git repository and Composer package. PilotPHP has no monorepo; every package is its own repository.