Novinky v Nette Schema 1.2

před 3 lety od David Grudl  

Nette Schema je nejmladší přírůstek do Nette rodiny. Knihovna vznikla původně pro potřeby Nette DI, tedy kvůli validaci a normalizaci vstupních konfiguračních souborů a informování o případných chybách.

Nette Schema se pokusilo najít srozumitelný a úsporný jazyk pro popis datových struktur. První verze pokrývala veškeré potřeby Nette DI a další vývoj pak řeší požadavky vzešlé od nových uživatelů a nasazení.

Připomeňme si, jak se vlastně Schema používá. Vytvoříme například schéma, kde vstup má být pole (či objekt s public properties, v terminologii Schema jde o strukturu), s povinným prvkem foo typu bool a nepovinným číselným bar a řetězcem baz:

use Nette\Schema\Expect;

$schema = Expect::structure([
    'foo' => Expect::bool()->required(),
    'bar' => Expect::int()->default(123),
    'baz' => Expect::string(),
]);

A validujeme vstupní $data:

$processor = new Nette\Schema\Processor;
$normalized = $processor->process($schema, $data);
// v případě chyby vyhodí Nette\Schema\ValidationException

Uvnitř structure() platí, že pokud neuvedeme jinak, tak každý prvek:

  • je nepovinný
  • má výchozí hodnotou null (resp. [] v případě polí)

Že má výchozí hodnotu null neznamená, že null také akceptuje – to by ve schématu by muselo být uvedeno např. Expect::int()->nullable().

Takže pro vstup $data = ['foo' => true] vrátí objekt {foo: true, bar: 123, baz: null}, viz demo. Tedy můžeme si sáhnout na $normalized->bar bez obav, že by property nebyla definovaná.

Výchozí hodnoty struktury

Pokud prvek akceptuje null a zároveň má výchozí hodnotu null, nelze odlišit výchozí hodnotu od předané. Pokud je to potřeba, lze od verze 1.2 výchozí hodnoty zcela vypnout pomocí skipDefaults():

$schema = Expect::structure([
	...
])->skipDefaults();

Takže pro vstup $data = ['foo' => true] vrátí objekt {foo: true}.

Povinné/nepovinné prvky

Jak bylo řečeno, povinné jsou jen prvky označené jako required(). Co ale pokud je struktura hlubší? Je prvek bar povinný?

$schema = Expect::structure([
	'foo' => Expect::int(),
	'inner' => Expect::structure([
		'bar' => Expect::string()->required(),
	]),
]);

Schema se v tomto případě snaží chovat tak, aby nezaskočilo programátora, takže prvek bar povinný je, byť striktně technicky by musel být i prvek inner označen za povinný. Schema ho učiní povinným implicitně. Viz demo.

Nyní lze označit strukturu za skutečně nepovinnou pomocí requred(false).

$schema = Expect::structure([
	'foo' => Expect::int(),
	'inner' => Expect::structure([
		'bar' => Expect::string()->required(),
	])->required(false),
]);

V takovém případě data nemusí obsahovat klíč inner, pokud ho však obsahují, musí být přítomen i podklíč bar.

Spojování prvků v polích

Jednou z věcí, která vzešla z chování Nette DI, ale pro uživatele samostatného Schema se ukázala jako překvapivá, bylo mergování výchozích a předaných hodnot v případě polí, tj. třeba Expect::array(), list() apod. Ve verzi Nette Schema 2 bude toto chování odstraněno, nyní jej lze vypnout:

Expect::list([1, 2, 3])->mergeDefaults(false)

arrayOf() a klíč

arrayOf říká, jaké prvky může obsahovat pole. Například pole, jehož prvky mohou být pouze řetězce, zapíšeme jako Expect::arrayOf('string').

Nově lze specifikovat i typ klíče druhým parametrem:

$schema = Expect::arrayOf('string', 'int');

Výchozí hodnota anyOf()

Nyní lze snadněji definovat výchozí hodnotu výčtu anyOf() tím, že za výchozí prohlásíme první položku:

Expect::anyOf(
	Expect::string()->default('def'),
	false,
)->firstIsDefault();

Výchozí hodnota takového prvku je 'def'.

Bohatší chybové hlášky

V případě, že data neprojdou validací, vyhodí se výjimka Nette\Schema\ValidationException. Její metoda getMessages() vrací pole všech chybových zpráv.

Novinkou je metoda getMessageObjects(), která vrací pole chyb jakožto objektů Nette\Schema\Message. V nich je zpráva rozložená na základní informace:

$messages = $e->getMessageObjects();
$message = $messages[0];
$message->message   // text zprávy s %placeholdery% pro proměnné
$message->code      // kód chyby, jedna z konstant v třídě Message
$message->path      // pole odkazující na prvek kterého se chyba týká
$message->variables // proměnné, které nahradí %placeholdery%

Díky tomu lze snadno zprávy třeba překládat do jiných jazyků.

Deprecated

A na závěr: prvky lze označovat jako deprecated s volitelnou chybovou hláškou. Seznam varování pak vrací getWarnings().

use Nette\Schema\Expect;

$schema = Expect::structure([
	'foo' => Expect::bool()->deprecated('Použijte místo toho `bar`'),
	'bar' => Expect::bool(),
]);

$normalized = $processor->process($schema, $data);
$warnings = $processor->getWarnings(); // vrací pole řetězců