Merging Configurations
Applications often assemble their configuration from several layers: built-in defaults, a configuration file shared by the team and a local file of each developer. Each layer overrides only the items it mentions. This page shows:
- how
processMultiple()merges the layers and validates the result - how structures, arrays and lists are combined
- how to choose another strategy with
mergeMode()or your own function - how arrays without described items are merged
The examples continue the configuration of an application from the overview. $processor is an
instance of Nette\Schema\Processor.
Merging Layers: processMultiple()
The method processMultiple() takes several datasets, merges them in order so that later ones take precedence, and
validates the final result as a whole:
$schema = Expect::structure([
'host' => Expect::string(),
'port' => Expect::int(),
'logging' => Expect::bool(),
]);
$defaults = ['host' => 'localhost', 'port' => 3306, 'logging' => false];
$local = ['port' => 5432, 'logging' => true];
$config = $processor->processMultiple($schema, [$defaults, $local]);
// $config = {'host' => 'localhost', 'port' => 5432, 'logging' => true}
The host item keeps its default value because the local file did not set it, while port and
logging are overwritten by the later dataset.
Notice that the result is validated only once, after merging. A layer on its own may be incomplete, for example the defaults usually lack the password, and only the merged configuration must contain everything that is required.
How Values Are Combined
The schema decides how two layers are combined:
- a simple value, such as a string or a number, is replaced by the later one
- a structure and an array with string keys are merged by keys, recursively according to the schemas of their items
- the items of a list (
listOf(), orarrayOf()with numeric keys) are appended one after another - a tuple is replaced as a whole, because mixing the positions of two tuples would only produce nonsense
So with 'admins' => Expect::listOf('email'), the administrators from the local file are added to those from the
shared one:
$schema = Expect::structure([
'admins' => Expect::listOf('email'),
]);
$config = $processor->processMultiple($schema, [
['admins' => ['jim@example.com']],
['admins' => ['ann@example.com']],
]);
// $config->admins = ['jim@example.com', 'ann@example.com']
The default value of an array is merged with the layers as well: its items stay unless a layer overrides them, and the items of the layers are appended to a list. To turn this off, call mergeDefaults(false).
Choosing a Strategy: mergeMode()
When appending or merging by keys is not what you want, arrays and structures can choose another strategy with
mergeMode(). Let's say the local file should replace the list of administrators instead of adding to it:
use Nette\Schema\MergeMode;
Expect::listOf('email')->mergeMode(MergeMode::Replace); // the later list replaces the earlier one
MergeMode::AppendKeys merges by keys and appends list items, OverwriteKeys also merges by keys but
overwrites list items by their position, and Replace keeps only the later value.
In a NEON configuration file of a Nette application, an exclamation mark after a key, admins!:, prevents the
merging of that one item, see configuration of the DI
container.
Arrays Without Described Items
An array whose items are not described, such as Expect::array(), is merged deeply, key by key at every level:
$schema = Expect::structure([
'options' => Expect::array(),
]);
$processor->processMultiple($schema, [
['options' => ['cache' => ['ttl' => 60]]],
['options' => ['cache' => ['ttl' => 3600]]],
]);
// OK, returns {'options' => ['cache' => ['ttl' => 3600]]}
Keep in mind that the schema then cannot check what the arrays contain. To let the later layer replace the array as a whole,
choose mergeMode(MergeMode::Replace).
Your Own Function: mergeWith()
For anything else, mergeWith() takes your own function that combines the later value with the earlier one.
Let's say logging should stay on once any layer turns it on:
Expect::bool()->mergeWith(fn($value, $base) => $value || $base); // once enabled, stays enabled
The function only combines layers; to adjust the shape of a single layer, use before().