Pop PHP
The Toolkit

Config Objects

pop-config wraps configuration data in an object that reads like an array, an object or a dot path, loads from five file formats, and refuses writes unless you ask for them. Pop\Application takes one directly, so the config an application boots with can be a Config instance rather than a bare array.

BASH
composer require popphp/pop-config

Creating a Config Object#

The constructor takes an array and gives you three ways to read it back:

PHP
use Pop\Config\Config;

$config = new Config([
    'foo'      => 'bar',
    'database' => ['host' => 'localhost', 'port' => 5432],
]);

$config->foo;               // 'bar'
$config['foo'];             // 'bar'
$config['database.host'];   // 'localhost'
$config->{'database.port'}; // 5432

Dot notation walks nested values without chaining array access at each level, and isset() understands it too. A literal key is matched first: if the data has an actual 'example.com' key, that key wins over traversing into an example array.

Config::createFromData() loads from a file instead, picking the parser from the extension:

PHP
use Pop\Config\Config;

$config = Config::createFromData(__DIR__ . '/../app/config/database.json');

$config['database.host'];

A path that does not exist throws Pop\Config\ParseException ("Error: The config file '...' does not exist."), and content that will not parse throws the same class ("Error: Unable to parse the config data from '...'."). An extension outside the five supported ones throws Pop\Config\UnsupportedFormatException.

createFromData() and parseData() take a file path. For config text you already hold, decode it and pass the array to the constructor.

Two conversions get the data back out. toArray() returns a plain array; toArrayObject() returns a Pop\Utils\ArrayObject, or a native \ArrayObject with ARRAY_AS_PROPS when passed true:

PHP
use Pop\Config\Config;

$config = new Config(['foo' => 'bar']);

$data              = $config->toArray();
$arrayObject       = $config->toArrayObject();
$nativeArrayObject = $config->toArrayObject(true);

Both objects are built from a fresh copy, so mutating one never reaches back into the Config.

Supported Formats#

Five formats parse and render: PHP, JSON, INI, XML and YAML. render() takes the format name and returns a string; writeToFile() picks the format from the filename's extension and writes it.

PHP
use Pop\Config\Config;

$config = new Config(['foo' => ['bar' => 1, 'baz' => 2]]);

echo $config->render('json');

$config->writeToFile(__DIR__ . '/../app/config/generated.json');

toJson(), toYaml(), toIni() and toXml() are the named equivalents of render(). An unrecognized format string or extension throws Pop\Config\UnsupportedFormatException ("Invalid type 'toml'. Supported config file types are PHP, JSON, YAML, INI or XML.").

The five are not equally faithful. PHP, JSON and YAML round-trip a nested array unchanged, types included; XML and INI do not.

Format Extensions Round-trips a nested array
PHP .php Yes, types preserved
JSON .json Yes, types preserved
YAML .yaml, .yml Yes, types preserved
XML .xml Structure yes, but every scalar comes back a string
INI .ini One level, types preserved — see the note below

INI flattens as it round-trips: render('ini') writes a nested array as an [foo] section whose keys repeat the section name. Use JSON or YAML when you need nesting preserved.

The YAML parser is symfony/yaml, a required dependency rather than a PHP extension, so YAML support is always available. Boolean words (yes, no, on, off, y, n, in any casing) and octal-looking integers such as 0755 are normalized to bool and int. One scalar is not normalized:

YAML
released: 2001-01-23
quoted: "2001-01-23"

released parses to the integer 980208000 — a Unix timestamp, not a string. quoted parses to the string '2001-01-23'. Quote any bare date you want to keep as text.

Merging and Immutability#

A Config is read-only by default. Setting or unsetting a value on one throws Pop\Config\ChangesNotAllowedException ("Real-time configuration changes are not allowed."), which is the point — configuration read at boot should not drift underneath the code that read it.

PHP
use Pop\Config\ChangesNotAllowedException;
use Pop\Config\Config;

$config = new Config(['foo' => 'bar']);

$config->changesAllowed(); // false

try {
    $config->foo = 'baz';
} catch (ChangesNotAllowedException $e) {
    // nothing was written
}

Pass true as the constructor's second argument for a config you intend to modify. Dot notation works for writes as well as reads:

PHP
use Pop\Config\Config;

$config = new Config(['database' => ['host' => 'localhost']], true);

$config['database.port'] = 5432; // ['database' => ['host' => 'localhost', 'port' => 5432]]

unset($config['database.host']); // ['database' => ['port' => 5432]]

A dotted key that does not already exist always creates nesting rather than a literal key — $config['example.com'] = 'x' on an empty config produces ['example' => ['com' => 'x']]. A literal key only wins when it is already in the data, having been loaded from a file that had it.

merge() folds another array in. By default the incoming values overwrite on a collision; pass true as the second argument and the existing values survive instead:

PHP
use Pop\Config\Config;

$config = new Config(['x' => 1, 'y' => ['a' => 1]], true);

$config->merge(['x' => 2, 'y' => ['b' => 2]]);
// ['x' => 2, 'y' => ['a' => 1, 'b' => 2]]

$config->merge(['x' => 99], true);
// x stays 2 — the existing value is preserved

mergeFromData() does the same from a file path, parsing it first. Both throw ChangesNotAllowedException on a config that does not allow changes, and mergeFromData() adds the ParseException and UnsupportedFormatException cases from loading.

Keep a key's shape consistent across the configs you merge — a list meeting an associative array at the same key produces a hybrid in both merge modes.

Where this fits in an application: config files hold data, and the application class consumes it. A route table, connection credentials and service definitions belong in app/config/; database initialization and service registration belong in load() on your Pop\Application subclass. A Config object is what carries the first across that line.

Rendering options, the to*() family and the full merge semantics go past what this page covers — see the pop-config README.

See Also#