Nette Schema
************
<div class=perex>
Čte vaše aplikace konfigurační soubor, nebo přijímá data z API? Než na taková data váš kód sáhne, měl by si ověřit, že mají tvar, jaký čeká. Nette Schema to udělá za vás:
- Očekávaná data popíšete pár řádky srozumitelného PHP kódu
- Ohlásí všechny chyby najednou, i s cestou k položce
- Doplní výchozí hodnoty, převede hodnoty a namapuje je na vaše objekty
- Sloučí konfiguraci z více souborů podle schématu
- Vyexportuje schéma do JSON Schema, takže editory nabízejí doplňování
</div>
Instalace
=========
Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]:
```shell
composer require nette/schema
```
První schéma
============
Nejlíp se to ukáže na příkladu. Dejme tomu, že se vaše aplikace připojuje k databázi a nastavení připojení čte z konfiguračního souboru. Po načtení je konfigurace obyčejné PHP pole, třeba:
```php
$data = [
'user' => 'admin',
'port' => 5432,
];
```
Nic nezaručuje, že soubor obsahuje to, co váš kód potřebuje. Někdo se může překlepnout v klíči, napsat port jako text nebo úplně zapomenout uživatelské jméno. Popíšeme si proto, jak mají data vypadat. Tomuto popisu se říká **schéma** a sestavuje ho třída [api:Nette\Schema\Expect]:
```php
use Nette\Schema\Expect;
$schema = Expect::structure([
'host' => Expect::string('localhost'),
'port' => Expect::int(3306),
'user' => Expect::string()->required(),
'password' => Expect::string()->nullable(),
]);
```
Čte se to skoro jako věta. Data jsou struktura se čtyřmi položkami: `host` je řetězec, a když chybí, je to `localhost`; `port` je celé číslo, výchozí 3306; `user` je řetězec, který musí být uvedený; a `password` je řetězec, který může být i `null`.
Teď necháme [api:Nette\Schema\Processor] data podle schématu zkontrolovat:
```php
$processor = new Nette\Schema\Processor;
$config = $processor->process($schema, $data);
echo $config->host; // 'localhost'
echo $config->port; // 5432
```
Výsledkem není stejné pole. Je to objekt `stdClass` se všemi čtyřmi položkami: chybějící `host` dostal výchozí hodnotu a chybějící `password` je `null`. Váš kód se může spolehnout, že tam každá položka je a má správný typ.
Když jsou data špatně
=====================
A co když konfigurace obsahuje chybu? Dejme tomu, že je port napsaný jako text a uživatel chybí:
```php
$data = [
'port' => 'abc',
];
try {
$config = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
echo implode("\n", $e->getMessages());
}
```
```
The item 'port' expects to be int, 'abc' given.
The mandatory item 'user' is missing.
```
Všimněte si, že dostanete všechny chyby najednou, ne jen tu první, a každá zpráva říká, které položky se týká. Uživatel opraví celý soubor naráz, místo aby spouštěl aplikaci pořád dokola.
Metoda `getMessages()` vrací zprávy jako řetězce. Pokud je chcete zobrazit po svém, `getMessageObjects()` vrací objekty, které nesou i cestu k položce a kód chyby. Víc o tom na stránce [Validace a normalizace |normalization#Chyby].
Popis dat
=========
Skutečná konfigurace má víc než čtyři položky. Přesuňme připojení k databázi do vnořené struktury a přidejme seznam e-mailů správců, režim, ve kterém aplikace běží, a datum, kdy se spouští:
```php
enum Mode: string
{
case Development = 'development';
case Production = 'production';
}
$schema = Expect::structure([
'database' => Expect::structure([
'host' => Expect::string('localhost'),
'port' => Expect::int(3306)->min(1)->max(65535),
'user' => Expect::string()->required(),
]),
'admins' => Expect::listOf('email'),
'mode' => Expect::enum(Mode::class)->default(Mode::Production),
'launchDate' => Expect::string()->nullable()->castTo(DateTimeImmutable::class),
]);
```
Každá položka přesně říká, co přijímá. Port musí být mezi 1 a 65535. `admins` je seznam, ve kterém musí být každá položka platná e-mailová adresa. `mode` přijme ze souboru řetězec `'development'` nebo `'production'` a váš kód dostane case enumu `Mode`. `launchDate` převede řetězec jako `'2026-03-01'` na objekt `DateTimeImmutable`.
Všechny datové typy a jejich volby, od čísel a řetězců po struktury a tuply, popisuje stránka [Datové typy |types].
Převod dat
==========
Validace je jen jedna polovina. Tou druhou je převést data do podoby, se kterou váš kód pracuje nejlíp. Místo `stdClass` budete nejspíš chtít vlastní třídu. `Expect::from()` z ní schéma vytvoří rovnou, podle typů jejích vlastností:
```php
class DatabaseConfig
{
public string $host = 'localhost';
public int $port = 3306;
public string $user;
public ?string $password = null;
}
$schema = Expect::from(new DatabaseConfig);
$config = $processor->process($schema, ['user' => 'admin']); // instance DatabaseConfig
```
Kromě toho můžete hodnotu před validací upravit pomocí `before()`, po ní změnit pomocí `transform()`, přetypovat pomocí `castTo()` a přidat vlastní kontroly pomocí `assert()`. To všechno najdete na stránce [Validace a normalizace |normalization].
Konfigurace ve vrstvách
=======================
Aplikace často skládají konfiguraci z více zdrojů: vestavěných výchozích hodnot, společného konfiguračního souboru a lokálního souboru každého vývojáře, přičemž každý přepíše jen ty položky, které uvádí. Metoda `processMultiple()` je sloučí v daném pořadí a výsledek zvaliduje jako celek:
```php
$defaults = ['host' => 'localhost', 'port' => 3306];
$local = ['user' => 'admin', 'port' => 5432];
$config = $processor->processMultiple($schema, [$defaults, $local]);
```
O tom, jak se vrstvy zkombinují, rozhoduje schéma, třeba se položky listu připojí jedna za druhou. Jak to funguje, popisuje stránka [Slučování konfigurací |merging].
JSON Schema pro váš editor
==========================
Vaše schéma už je přesným popisem toho, co smí konfigurace obsahovat. `Nette\Schema\JsonSchema::export()` z něj vytvoří dokument JSON Schema, podle kterého editory nabízejí doplňování a podtrhávají chyby přímo v konfiguračním souboru. Viz [Export do JSON Schema |json-schema].
Nette Schema v aplikacích Nette
===============================
Knihovnu používáte, i když ji sami nevoláte: každé rozšíření [DI kontejneru |dependency-injection:] popisuje svou sekci konfigurace schématem v metodě [getConfigSchema() |dependency-injection:extensions#getConfigSchema]. Proto se překlep v konfiguraci vaší aplikace ohlásí srozumitelnou zprávou.
Co dál
======
- [Datové typy |types] - všechny prvky schématu a jejich volby
- [Validace a normalizace |normalization] - vlastní kontroly, transformace, přetypování a mapování na objekty
- [Slučování konfigurací |merging] - kombinování vrstev konfigurace
- [Export do JSON Schema |json-schema] - doplňování a kontrola konfigurace v editorech
- [Vlastní prvky |custom-elements] - jak zpracování probíhá a jak napsat vlastní prvek
{{maintitle: Nette Schema: validace a normalizace datových struktur v PHP}}
{{description: Praktická PHP knihovna pro validaci a normalizaci dat oproti schématu s chytrým a srozumitelným API. Hodí se třeba pro konfiguraci nebo vstupy z API.}}
Nette Schema
Čte vaše aplikace konfigurační soubor, nebo přijímá data z API? Než na taková data váš kód sáhne, měl by si
ověřit, že mají tvar, jaký čeká. Nette Schema to udělá za vás:
- Očekávaná data popíšete pár řádky srozumitelného PHP kódu
- Ohlásí všechny chyby najednou, i s cestou k položce
- Doplní výchozí hodnoty, převede hodnoty a namapuje je na vaše objekty
- Sloučí konfiguraci z více souborů podle schématu
- Vyexportuje schéma do JSON Schema, takže editory nabízejí doplňování
Instalace
Knihovnu stáhnete a nainstalujete pomocí nástroje Composer:
composer require nette/schema
První schéma
Nejlíp se to ukáže na příkladu. Dejme tomu, že se vaše aplikace připojuje k databázi a nastavení připojení čte
z konfiguračního souboru. Po načtení je konfigurace obyčejné PHP pole, třeba:
$data = [
'user' => 'admin',
'port' => 5432,
];
Nic nezaručuje, že soubor obsahuje to, co váš kód potřebuje. Někdo se může překlepnout v klíči, napsat port jako
text nebo úplně zapomenout uživatelské jméno. Popíšeme si proto, jak mají data vypadat. Tomuto popisu se říká
schéma a sestavuje ho třída Nette\Schema\Expect:
use Nette\Schema\Expect;
$schema = Expect::structure([
'host' => Expect::string('localhost'),
'port' => Expect::int(3306),
'user' => Expect::string()->required(),
'password' => Expect::string()->nullable(),
]);
Čte se to skoro jako věta. Data jsou struktura se čtyřmi položkami: host je řetězec, a když chybí, je to
localhost; port je celé číslo, výchozí 3306; user je řetězec, který musí být
uvedený; a password je řetězec, který může být i null.
Teď necháme Nette\Schema\Processor data podle
schématu zkontrolovat:
$processor = new Nette\Schema\Processor;
$config = $processor->process($schema, $data);
echo $config->host; // 'localhost'
echo $config->port; // 5432
Výsledkem není stejné pole. Je to objekt stdClass se všemi čtyřmi položkami: chybějící host
dostal výchozí hodnotu a chybějící password je null. Váš kód se může spolehnout, že tam
každá položka je a má správný typ.
Když jsou data špatně
A co když konfigurace obsahuje chybu? Dejme tomu, že je port napsaný jako text a uživatel chybí:
$data = [
'port' => 'abc',
];
try {
$config = $processor->process($schema, $data);
} catch (Nette\Schema\ValidationException $e) {
echo implode("\n", $e->getMessages());
}
The item 'port' expects to be int, 'abc' given.
The mandatory item 'user' is missing.
Všimněte si, že dostanete všechny chyby najednou, ne jen tu první, a každá zpráva říká, které položky se týká.
Uživatel opraví celý soubor naráz, místo aby spouštěl aplikaci pořád dokola.
Metoda getMessages() vrací zprávy jako řetězce. Pokud je chcete zobrazit po svém,
getMessageObjects() vrací objekty, které nesou i cestu k položce a kód chyby. Víc o tom na stránce Validace a normalizace.
Popis dat
Skutečná konfigurace má víc než čtyři položky. Přesuňme připojení k databázi do vnořené struktury a přidejme
seznam e-mailů správců, režim, ve kterém aplikace běží, a datum, kdy se spouští:
enum Mode: string
{
case Development = 'development';
case Production = 'production';
}
$schema = Expect::structure([
'database' => Expect::structure([
'host' => Expect::string('localhost'),
'port' => Expect::int(3306)->min(1)->max(65535),
'user' => Expect::string()->required(),
]),
'admins' => Expect::listOf('email'),
'mode' => Expect::enum(Mode::class)->default(Mode::Production),
'launchDate' => Expect::string()->nullable()->castTo(DateTimeImmutable::class),
]);
Každá položka přesně říká, co přijímá. Port musí být mezi 1 a 65535. admins je seznam, ve kterém
musí být každá položka platná e-mailová adresa. mode přijme ze souboru řetězec 'development'
nebo 'production' a váš kód dostane case enumu Mode. launchDate převede řetězec jako
'2026-03-01' na objekt DateTimeImmutable.
Všechny datové typy a jejich volby, od čísel a řetězců po struktury a tuply, popisuje stránka Datové typy.
Převod dat
Validace je jen jedna polovina. Tou druhou je převést data do podoby, se kterou váš kód pracuje nejlíp. Místo
stdClass budete nejspíš chtít vlastní třídu. Expect::from() z ní schéma vytvoří rovnou, podle
typů jejích vlastností:
class DatabaseConfig
{
public string $host = 'localhost';
public int $port = 3306;
public string $user;
public ?string $password = null;
}
$schema = Expect::from(new DatabaseConfig);
$config = $processor->process($schema, ['user' => 'admin']); // instance DatabaseConfig
Kromě toho můžete hodnotu před validací upravit pomocí before(), po ní změnit pomocí
transform(), přetypovat pomocí castTo() a přidat vlastní kontroly pomocí assert(). To
všechno najdete na stránce Validace a normalizace.
Konfigurace ve vrstvách
Aplikace často skládají konfiguraci z více zdrojů: vestavěných výchozích hodnot, společného konfiguračního
souboru a lokálního souboru každého vývojáře, přičemž každý přepíše jen ty položky, které uvádí. Metoda
processMultiple() je sloučí v daném pořadí a výsledek zvaliduje jako celek:
$defaults = ['host' => 'localhost', 'port' => 3306];
$local = ['user' => 'admin', 'port' => 5432];
$config = $processor->processMultiple($schema, [$defaults, $local]);
O tom, jak se vrstvy zkombinují, rozhoduje schéma, třeba se položky listu připojí jedna za druhou. Jak to funguje,
popisuje stránka Slučování konfigurací.
JSON Schema pro váš editor
Vaše schéma už je přesným popisem toho, co smí konfigurace obsahovat. Nette\Schema\JsonSchema::export()
z něj vytvoří dokument JSON Schema, podle kterého editory nabízejí doplňování a podtrhávají chyby přímo
v konfiguračním souboru. Viz Export do JSON Schema.
Nette Schema v aplikacích Nette
Knihovnu používáte, i když ji sami nevoláte: každé rozšíření DI kontejneru popisuje svou sekci konfigurace schématem v metodě getConfigSchema(). Proto se překlep
v konfiguraci vaší aplikace ohlásí srozumitelnou zprávou.
Co dál