Slučování konfigurací

Aplikace často skládají konfiguraci z více vrstev: vestavěných výchozích hodnot, konfiguračního souboru sdíleného týmem a lokálního souboru každého vývojáře. Každá vrstva přepíše jen ty položky, které uvádí. Tato stránka ukazuje:

  • jak processMultiple() vrstvy sloučí a výsledek zvaliduje
  • jak se kombinují struktury, pole a listy
  • jak zvolit jinou strategii pomocí mergeMode() nebo vlastní funkce
  • jak se slučují pole, jejichž položky nejsou popsané

Ukázky navazují na konfiguraci aplikace z úvodu. $processor je instance Nette\Schema\Processor.

Slučování vrstev: processMultiple()

Metoda processMultiple() vezme několik datových sad, sloučí je v pořadí tak, že pozdější mají přednost, a výsledek zvaliduje jako celek:

$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}

Položka host si ponechá výchozí hodnotu, protože ji lokální soubor nenastavil, zatímco port a logging přepíše pozdější datová sada.

Všimněte si, že se výsledek validuje jen jednou, až po sloučení. Samotná vrstva může být neúplná, třeba výchozí hodnotám obvykle chybí heslo, a všechno povinné musí obsahovat až sloučená konfigurace.

Jak se hodnoty kombinují

O tom, jak se dvě vrstvy zkombinují, rozhoduje schéma:

  • jednoduchou hodnotu, třeba řetězec nebo číslo, nahradí ta pozdější
  • struktura a pole s řetězcovými klíči se slučují podle klíčů, rekurzivně podle schémat svých položek
  • položky listu (listOf(), případně arrayOf() s číselnými klíči) se připojují jedna za druhou
  • tuple se nahradí celý, protože smíchat pozice dvou tuplů by dalo jen nesmysl

Takže s 'admins' => Expect::listOf('email') se správci z lokálního souboru přidají k těm ze sdíleného:

$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']

Výchozí hodnota pole se s vrstvami také slučuje: její položky zůstanou, pokud je některá vrstva nepřepíše, a k listu se položky vrstev připojí. Pokud to nechcete, zavolejte mergeDefaults(false).

Volba strategie: mergeMode()

Pokud připojování nebo slučování podle klíčů není to, co chcete, mohou si pole a struktury zvolit jinou strategii metodou mergeMode(). Dejme tomu, že lokální soubor má seznam správců nahradit, ne k němu přidávat:

use Nette\Schema\MergeMode;

Expect::listOf('email')->mergeMode(MergeMode::Replace); // pozdější list nahradí ten dřívější

MergeMode::AppendKeys slučuje podle klíčů a položky listu připojuje, OverwriteKeys také slučuje podle klíčů, ale položky listu přepisuje podle jejich pozice, a Replace ponechá jen pozdější hodnotu.

V konfiguračním souboru NEON aplikace Nette zabrání vykřičník za klíčem, admins!:, sloučení té jedné položky, viz konfigurace DI kontejneru.

Pole bez popsaných položek

Pole, jehož položky nejsou popsané, třeba Expect::array(), se slučuje do hloubky, podle klíčů na všech úrovních:

$schema = Expect::structure([
	'options' => Expect::array(),
]);

$processor->processMultiple($schema, [
	['options' => ['cache' => ['ttl' => 60]]],
	['options' => ['cache' => ['ttl' => 3600]]],
]);
// OK, vrátí {'options' => ['cache' => ['ttl' => 3600]]}

Počítejte s tím, že schéma pak nemůže zkontrolovat, co pole obsahují. Pokud má pozdější vrstva pole nahradit celé, zvolte mergeMode(MergeMode::Replace).

Vlastní funkce: mergeWith()

Pro cokoliv jiného je tu mergeWith(), kterému předáte vlastní funkci, jež pozdější hodnotu zkombinuje s tou dřívější. Dejme tomu, že logování má zůstat zapnuté, jakmile ho kterákoliv vrstva zapne:

Expect::bool()->mergeWith(fn($value, $base) => $value || $base); // jednou zapnuto, zůstane zapnuto

Funkce jen kombinuje vrstvy; pokud chcete upravit tvar jedné vrstvy, použijte before().

verze: 2.x 1.x