Search by

laruence / yaconf

laruence

A PHP Persistent Configurations Container

Package info

github.com/laruence/yaconf

Type:php-ext

Ext name:ext-yaconf

pkg:composer/laruence/yaconf

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1 045

Open Issues: 0

1.2.0 2026-08-18 04:28 UTC

This package is auto-updated.

Last update: 2026-09-21 06:46:29 UTC


README

AppVeyor Linux Windows

A PHP Persistent Configuration Container

Requirement

  • PHP 7+
  • Optional YAML support: libyaml headers/library plus --with-yaml or --with-yaml=/prefix

Introduction

Yaconf is a configuration container. It parses INI files by default, with optional YAML support backed directly by libyaml, and stores the result in PHP at startup. Configurations live in persistent memory across the entire PHP lifecycle, which makes it very fast.

Yaconf uses an immutable data + Copy-on-Write design rather than shared memory (shmget/mmap). Parsed configs are stored in persistent zend_arrays marked IS_ARRAY_IMMUTABLE — and all keys are interned as permanent strings. Because the hash tables are immutable, PHP-FPM workers forked from the master process share the same physical memory pages via the OS kernel's COW mechanism. As long as the configuration doesn't change, memory is allocated only once — no matter how many workers are running. When a config file is modified and Yaconf reloads it (in non-ZTS mode), the kernel copies only the changed pages on write, isolating the new config from the old.

Since 1.2.0, once parsing finishes Yaconf compacts the whole config tree into a single contiguous block (two-phase compaction): it walks the parsed tree, collects every string and hash table, allocates one block, and copies them in — deduplicating strings by content and re-laying out every hash table into canonical engine layout. All the scattered per-node allocations from the parse phase are freed. The win is twofold: fewer individual allocations means lower memory overhead, and a contiguous layout means better cache locality and fewer touched pages when workers COW the config. When a config file changes, Yaconf rebuilds and re-compacts from scratch; tables that need to grow are detached onto the persistent heap first so the engine can resize them without touching the block.

⚠ ZTS (Thread-Safe) builds: Yaconf loads configurations at startup as usual, but automatic reloading is not available (yaconf.check_delay is NTS-only). Restart PHP to pick up config changes.

When to use Yaconf

Most PHP applications have configuration files that get parsed on every request. Every request pays the I/O and parse cost, then throws the result away — only to do it again on the next request.

Yaconf flips this: parse once at startup, serve from memory forever. The parsed config lives in persistent zend_arrays with immutable hash tables. Yaconf::get() is a pure hash lookup — no file I/O, no parsing, no memory allocation per request.

  • Best for: Read-heavy config that changes infrequently — database credentials, feature flags, routing tables, service discovery maps. Anything you parse on every request today.
  • Not ideal for: Config that changes per-request or per-user. Dynamic configuration that needs runtime computation (Yaconf stores static values — INI constants and environment variables are resolved once during parsing, not on access).
  • Scale: The memory overhead is minimal — a few KB per configuration file, shared across all workers via COW until the config changes. There's no practical limit on the number of supported configuration files beneath yaconf.directory.

Yaconf is for static configuration. For runtime caching — database query results, computed data, HTML fragments, ephemeral tokens — use Yac, which shares the same "local first, zero dependency" design philosophy.

What's new in 1.2.0

  • Sub-directory support: supported configuration files in sub-directories are loaded recursively (up to 16 levels) and namespaced by the directory name — sub/x.ini is addressed as "sub.x". Sub-directories are tracked for hot reload too.
  • Compact block storage: all parsed configurations are consolidated into a single contiguous block after startup (see the Introduction) — lower memory overhead, better cache locality, and fewer pages touched when workers COW.
  • PHP PIE support: installable via PIE, the PHP Installer for Extensions.
  • A name conflict between a supported configuration file and a same-named directory raises a warning; the directory wins and the file is skipped.
  • Fixed memory leaks when a dot-notation key overrides a scalar value, and on foreach-by-ref over compact block tables with PHP 7.0.
  • Yaconf::__debug_info() now reports the stored value's address.

Features

  • Fast, light
  • Zero-copy when accessing configurations
  • Configs consolidated into one compacted block — lower memory, better cache locality (since 1.2.0)
  • INI sections and section inheritance (up to 16 levels deep)
  • Sub-directories of arbitrary depth (up to 16 levels) — sub/x.ini is addressed as "sub.x" (since 1.2.0)
  • Configurations reload automatically after changes (non-ZTS only), including sub-directories
  • C API exported for use by other PHP extensions

Install

Install via PECL

Yaconf is a PECL extension, simply install it by:

$ pecl install yaconf

Install via PIE (since 1.2.0)

Yaconf can also be installed with PIE, the PHP Installer for Extensions:

$ pie install laruence/yaconf

Compile from source

$ /path/to/phpize
$ ./configure --with-php-config=/path/to/php-config
# Optional YAML support, linked directly against the system libyaml:
$ ./configure --with-php-config=/path/to/php-config --with-yaml
# Or use a libyaml installation prefix:
$ ./configure --with-php-config=/path/to/php-config --with-yaml=/path/to/libyaml-prefix
$ make && make install

YAML support requires matching libyaml headers and libraries. On Windows, --with-yaml is enabled only when matching libyaml development inputs are available; otherwise the build warns and remains INI-only.

Runtime Configuration

INI Setting Default Description
yaconf.directory "" Path to the directory where supported configuration files are placed. .ini is always supported; .yaml and .yml require a build configured with --with-yaml. Sub-directories are loaded recursively (up to 16 levels deep).
yaconf.check_delay 300 Interval in seconds at which Yaconf checks for config file changes (by comparing directory mtimes — first the configured directory, then each tracked sub-directory). Set to 0 to check on every request. Only available in non-ZTS builds. In ZTS builds, configurations are still loaded at startup, but automatic reloading is disabled — restart PHP to pick up changes.

Constants

Yaconf always registers YACONF_HAVE_YAML, a boolean indicating whether YAML support was compiled into this build. Builds without --with-yaml set it to false and ignore .yaml and .yml files.

APIs

All Yaconf methods are static — you call them on the class directly, not on an instance.

Yaconf::get

static mixed Yaconf::get(string $name, mixed $default = null)

Fetches a configuration value by its $name. The $name uses dot notation to traverse nested keys (e.g. "foo.name", "foo.features.1", "sub.x.role"). The maximum nesting depth is 64.

Returns the configuration value on success, or $default (which defaults to null) if the key is not found.

Yaconf::has

static bool Yaconf::has(string $name)

Returns true if a configuration value exists at $name, false otherwise.

<?php
var_dump(Yaconf::has("foo.name"));      // bool(true)
var_dump(Yaconf::has("foo.not_exist")); // bool(false)

C API for Other Extensions

Yaconf exports two functions via php_yaconf.h for use by other PHP extensions:

PHP_YACONF_API zval *php_yaconf_get(zend_string *name);
PHP_YACONF_API int    php_yaconf_has(zend_string *name);

These mirror Yaconf::get() and Yaconf::has() in C. The header is installed by make install — include it in your extension with #include "ext/yaconf/php_yaconf.h".

Example

Directory

Assuming we place all configuration files in /tmp/yaconf/, add this to php.ini:

yaconf.directory=/tmp/yaconf

Supported Configuration Files

Yaconf always loads .ini files. Builds configured with --with-yaml also load .yaml and .yml. Files are loaded recursively from sub-directories (up to 16 levels deep). A sub-directory acts as a namespace: its name becomes a key level, and files (and further sub-directories) inside it nest below that key.

YAML files must have a mapping root. YAML mappings become PHP arrays; YAML lists retain numeric keys, so app.items.0 addresses the first item. YAML scalar types (string, int, finite float, bool, null) are preserved. YAML uses a static, native-parsed subset: exactly one document, no custom/timestamp/binary tags, aliases, shared nodes, complex keys, objects, resources, or references.

A supported file is keyed by its basename: app.ini, app.yaml, and app.yml all map to app. When same-directory enabled formats share a basename, Yaconf loads the first file found by its stable alphabetical scan and emits one warning while skipping later files; it does not hard-code an extension priority. A directory with that basename takes precedence over every supported file.

INI Files

Assuming there are two files in /tmp/yaconf:

foo.ini

name="yaconf"                  ; string
year=2015                      ; number
features[]="fast"              ; map
features.1="light"
features.plus="zero-copy"
features.constant=PHP_VERSION  ; PHP constants are resolved
features.env=${HOME}           ; environment variables are resolved

bar.ini

[base]
parent="yaconf"
children="NULL"

[children:base]               ; inherits from section "base"
children="set"

The [children:base] syntax means: the children section inherits all keys from the base section, and can override any of them. Section inheritance can be chained (e.g. [grandchild:children] inheriting from a section that itself inherits from base), up to a maximum depth of 16.

Run

Let's retrieve the configurations from Yaconf:

foo.ini

$ php -r 'var_dump(Yaconf::get("foo"));'
/*
array(3) {
  ["name"]=>
  string(6) "yaconf"
  ["year"]=>
  string(4) "2015"
  ["features"]=>
  array(5) {
    [0]=>
    string(4) "fast"
    [1]=>
    string(5) "light"
    ["plus"]=>
    string(9) "zero-copy"
    ["constant"]=>
    string(9) "7.0.0-dev"
    ["env"] =>
    string(16) "/home/huixinchen"
  }
}
*/

As you can see, Yaconf supports string, map (array), INI section inheritance, environment variables, and PHP constants.

You can also access configurations using dot notation:

$ php -r 'var_dump(Yaconf::get("foo.name"));'
// string(6) "yaconf"

$ php -r 'var_dump(Yaconf::get("foo.features.1"));'
// string(5) "light"

$ php -r 'var_dump(Yaconf::get("foo.features")["plus"]);'
// string(9) "zero-copy"

bar.ini

Now let's see sections and section inheritance:

$ php -r 'var_dump(Yaconf::get("bar"));'
/*
array(2) {
  ["base"]=>
  array(2) {
    ["parent"]=>
    string(6) "yaconf"
    ["children"]=>
    string(4) "NULL"
  }
  ["children"]=>
  array(2) {
    ["parent"]=>
    string(6) "yaconf"
    ["children"]=>
    string(3) "set"
  }
}
*/

The children section inherits values from the base section, and can override the values it wants to change.

Sub-directories

Sub-directory support is available since 1.2.0.

Now assume /tmp/yaconf also contains sub-directories:

/tmp/yaconf/
├── foo.ini
├── bar.ini
└── sub/
    ├── x.ini        ; role="assistant"
    └── deep/
        └── y.ini    ; level="three"

Each sub-directory becomes a key level; files inside it are addressed with the directory name as a prefix — at any depth:

$ php -r 'var_dump(Yaconf::get("sub.x.role"));'
// string(9) "assistant"

$ php -r 'var_dump(Yaconf::get("sub.deep.y.level"));'
// string(5) "three"

Fetching a directory name alone returns the whole directory as an array:

$ php -r 'var_dump(array_keys(Yaconf::get("sub")));'
/*
array(2) {
  [0]=>
  string(4) "deep"
  [1]=>
  string(1) "x"
}
*/

A directory and any supported file with the same basename would claim the same key; the directory wins — Yaconf emits one warning, skips every matching supported file, and retains the directory namespace.

phpinfo() Output

When yaconf.check_delay is non-zero, Yaconf adds a block to phpinfo() showing the directory being watched, the configured check delay, a list of all currently loaded supported configuration files (with their path relative to yaconf.directory) and their last modification time, plus a list of all tracked sub-directories.

License

PHP-3.01