Nette Documentation Preview

syntax
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