Datové typy

Schéma se skládá z prvků a každý z nich popisuje jednu část dat. Tato stránka je prochází všechny:

  • jednoduché typy, formáty a rozsahy
  • výchozí hodnoty, povinné položky a položky připouštějící null
  • pole, listy, výčty a enumy
  • struktury a tuply
  • popisy a zastaralé položky

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

Jednoduché typy: type()

Ve schématu lze uvést všechny standardní datové typy PHP:

Expect::string($default = null);
Expect::int($default = null);
Expect::float($default = null);
Expect::bool($default = null);
Expect::null();
Expect::array($default = []);
Expect::list($default = []);

A dále všechny typy, podporované třídou Validators, například Expect::type('scalar') nebo zkráceně Expect::scalar(). Také názvy tříd či rozhraní, například Expect::type('AddressEntity').

Lze použít i union zápis:

Expect::type('bool|string|array')

Výchozí hodnota je vždy null s výjimkou pro array a list, kde je to prázdné pole. (List je pole indexované podle vzestupné řady numerických klíčů od nuly, tedy neasociativní pole).

Ve verzi 1.x znamená typ '?int[]' pole, jehož položky jsou celá čísla nebo null. Verze 2.0 ho čte stejně jako phpDoc, tedy jako pole celých čísel, nebo null místo celého pole. Pokud myslíte pole, jehož položky mohou být null, napište Expect::arrayOf(Expect::int()->nullable()), což znamená totéž v obou verzích.

Výchozí, povinné a nullable

Každá položka, která ve vstupu chybí, dostane výchozí hodnotu. U většiny typů je to null a jinou nastavíte jako parametr nebo pomocí default():

Expect::int(3306);
Expect::int()->default(3306); // totéž

Výchozí hodnota null neznamená, že vstup smí obsahovat null. Expect::string() přijme jen řetězec, takže 'password' => null ve vstupu je chyba. Pokud chcete null povolit, zavolejte nullable():

$schema = Expect::structure([
	'user' => Expect::string(),
	'password' => Expect::string()->nullable(),
]);

$processor->process($schema, ['user' => null]);
// CHYBA: The item 'user' expects to be string, null given.

$processor->process($schema, ['password' => null]);
// OK, vrací {'password' => null, 'user' => null}

Položku, která musí být uvedená, označíte pomocí required(). Když chybí, zpracování ohlásí chybu, místo aby použilo výchozí hodnotu:

$schema = Expect::structure([
	'user' => Expect::string()->required(),
]);

$processor->process($schema, []);
// CHYBA: The mandatory item 'user' is missing.

Rozsahy: min() max()

Pomocí min() a max() lze u polí omezit počet prvků:

// pole, nejméně 1 položka, maximálně 5 položek
Expect::listOf('email')->min(1)->max(5);

U řetězců omezit jejich délku:

// řetězec, nejméně 8 bajtů dlouhý, maximálně 64 bajtů
Expect::string()->min(8)->max(64);

Délka řetězce se měří v bajtech. Pokud chcete u řetězce v UTF-8 počítat znaky, použijte Expect::unicode().

U čísel omezit jejich hodnotu:

// celé číslo, mezi 1 a 65535 včetně
Expect::int()->min(1)->max(65535);

Samozřejmě je možné uvést pouze min(), nebo pouze max():

// řetězec maximálně 64 bajtů
Expect::string()->max(64);

U unionu jako Expect::type('int|string') není jasné, zda mají min() a max() omezovat číslo, nebo délku řetězce, proto tam vyvolají varování E_USER_DEPRECATED. Omezení místo toho dejte jednotlivým variantám anyOf():

Expect::anyOf(Expect::int()->min(1), Expect::string()->min(1));

Regulární výrazy: pattern()

Pomocí pattern() lze uvést regulární výraz, kterému musí odpovídat celý vstupní řetězec (tj. jako by byl obalen znaky ^ a $):

// právě 9 čísel
Expect::string()->pattern('\d{9}');

Regulární výraz má smysl jen u řetězce. Proto Expect::string() vrací prvek třídy StringType, zatímco Expect::int() a Expect::float() vracejí NumberType a Expect::listOf() i ostatní schémata polí ArrayType, a každý z nich nabízí metody svého druhu. Volání pattern() u čísla nebo pole vyvolá varování E_USER_DEPRECATED a editor vás na zastaralé volání upozorní hned při psaní.

Pole a listy: arrayOf() listOf()

Pole představuje příliš obecnou strukturu, užitečnější je určit, jaké prvky přesně může obsahovat. Například pole, jehož prvky mohou být pouze řetězce:

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
$processor->process($schema, ['key' => 123]); // CHYBA: 123 není string

Druhým parametrem lze určit klíče (od verze 1.2):

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

$processor->process($schema, ['hello', 'world']); // OK
$processor->process($schema, ['a' => 'hello']); // CHYBA: 'a' není int

List je indexované pole:

$schema = Expect::listOf('email');

$processor->process($schema, ['jim@example.com', 'ann@example.com']); // OK
$processor->process($schema, ['jim@example.com', 'jim']); // CHYBA: 'jim' není e-mail
$processor->process($schema, ['key' => 'jim@example.com']); // CHYBA: není list

S parametrem wrap se přijme i samostatná hodnota a zabalí se do listu o jedné položce. Konfigurace s jediným správcem pak nepotřebuje závorky:

$schema = Expect::listOf('email', wrap: true);

$processor->process($schema, 'jim@example.com'); // OK, vrátí ['jim@example.com']

Parametrem může být i schéma, můžeme tedy zapsat:

Expect::arrayOf(Expect::bool())

Výchozí hodnota je prázdné pole. Pokud zadáte výchozí hodnotu, bude sloučena s předanými daty. To lze deaktivovat pomocí mergeDefaults(false) (od verze 1.1).

Výčet: anyOf()

anyOf() představuje výčet hodnot nebo schémat, kterých může hodnota nabývat. Takto zapíšeme pole prvků, které mohou být buď 'a', true nebo null:

$schema = Expect::listOf(
	Expect::anyOf('a', true, null),
);

$processor->process($schema, ['a', true, null, 'a']); // OK
$processor->process($schema, ['a', false]); // CHYBA: false tam nepatří

Prvky výčtu mohou být i schémata:

$schema = Expect::listOf(
	Expect::anyOf(Expect::string(), true, null),
);

$processor->process($schema, ['foo', true, null, 'bar']); // OK
$processor->process($schema, [123]); // CHYBA

Metoda anyOf() přijímá varianty jako jednotlivé parametry, nikoliv pole. Pokud jí chcete předat pole hodnot, použijte unpacking operátor anyOf(...$variants).

Varianty se zkoušejí v pořadí, v jakém jsou zapsané, a vyhraje první, která vyhoví. Jedinou výjimkou je null: pokud ho výčet přijímá, vstup null dá vždy null, i když by ho dříve uvedená varianta se strukturou nebo polem jinak převedla na prázdnou strukturu či pole.

Výchozí hodnota je null. Metodou firstIsDefault() učiníme první prvek výchozí:

// výchozí je 'hello'
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();

Výčtové typy: enum()

Konfigurační soubor obsahuje jen prostý řetězec jako 'production', jenže v kódu byste mnohem raději pracovali s enumem. Metoda enum() jedno převede na druhé: předáte jí enum, jehož case mají přiřazené hodnoty (enum Mode: string), a ona přijme buď tuto hodnotu, nebo přímo case, a vždy vám vrátí case:

enum Mode: string
{
	case Development = 'development';
	case Production = 'production';
}

$schema = Expect::enum(Mode::class);

$processor->process($schema, 'production');      // OK, vrátí Mode::Production
$processor->process($schema, Mode::Development); // OK, vrátí Mode::Development
$processor->process($schema, 'test');            // CHYBA: expects to be Mode

Enumy s celočíselnými hodnotami fungují úplně stejně a platí pro ně vše, co znáte z ostatních prvků, třeba Expect::enum(Mode::class)->required() nebo ->default(Mode::Production).

Při generování schématu ze třídy vám vlastnost public Mode $mode dá právě tento prvek, takže tam po enum() sahat nemusíte. Expect::type(Mode::class) je něco jiného: jako u každého názvu třídy přijme jen instanci, tedy samotný case.

Struktury

Struktury jsou objekty s definovanými klíči. Každá z dvojic klíč ⇒ hodnota je označována jako „vlastnost“.

Struktury přijímají pole a objekty a vrací objekty stdClass. Pokud chcete místo toho pole, doplňte castTo('array').

Ve výchozím nastavení jsou všechny vlastnosti volitelné a mají výchozí hodnotu null. Povinné vlastnosti můžete definovat pomocí required():

$schema = Expect::structure([
	'user' => Expect::string()->required(),
	'password' => Expect::string(), // výchozí hodnota je null
]);

$processor->process($schema, ['password' => 'secret']);
// CHYBA: The mandatory item 'user' is missing.

$processor->process($schema, ['user' => 'admin']);
// OK, vrací {'user' => 'admin', 'password' => null}

Samotná struktura je povinná. Pokud je tedy vnořená v jiné struktuře a vstup ji neobsahuje, přesto se vytvoří – a ohlásí chybu ve chvíli, kdy obsahuje povinnou vlastnost. Pomocí required(false) učiníte celou vnořenou strukturu volitelnou. Když ve vstupu chybí, na výstupu bude null, ale pokud je přítomna, její povinné vlastnosti se vyžadují:

$schema = Expect::structure([
	'database' => Expect::structure([
		'user' => Expect::string()->required(),
	])->required(false),
]);

$processor->process($schema, []);
// OK, vrací {'database' => null}

$processor->process($schema, ['database' => []]);
// CHYBA: The mandatory item 'database › user' is missing.

Pokud nechcete mít na výstupu vlastnosti s výchozí hodnotou, použijte skipDefaults():

$schema = Expect::structure([
	'user' => Expect::string()->required(),
	'password' => Expect::string(),
])->skipDefaults();

$processor->process($schema, ['user' => 'admin']);
// OK, vrací {'user' => 'admin'}

Pole všech vlastností struktury vrací metoda getShape().

Ve výchozím nastavení nemohou být ve vstupních datech žádné položky navíc. Právě tak se odhalí překlep v klíči:

$schema = Expect::structure([
	'host' => Expect::string(),
]);

$processor->process($schema, ['hots' => 'localhost']);
// CHYBA: Unexpected item 'hots', did you mean 'host'?

Což můžeme změnit pomocí otherItems(). Jako parametr uvedeme schéma, podle kterého se budou prvky navíc validovat:

$schema = Expect::structure([
	'host' => Expect::string(),
])->otherItems(Expect::int());

$processor->process($schema, ['timeout' => 30]); // OK
$processor->process($schema, ['timeout' => true]); // CHYBA

Novou strukturu můžete vytvořit odvozením od jiné pomocí extend():

$database = Expect::structure([
	'host' => Expect::string('localhost'),
	'port' => Expect::int(3306),
]);

$replica = $database->extend([
	'readOnly' => Expect::bool(true),
]);

Pole

Pole s definovanými klíči. Platí pro něj vše, co pro struktury.

$schema = Expect::array([
	'user' => Expect::string()->required(),
	'password' => Expect::string(), // výchozí hodnota je null
]);

Tuple: tuple()

Tuple je pole pevné velikosti, v němž má každá pozice své vlastní schéma:

$schema = Expect::tuple([
	Expect::int(),
	Expect::string(),
	Expect::bool(),
]);

$processor->process($schema, [1, 'hello', true]); // OK

Vstupem musí být list, takže pozice zadané klíči nebo v jiném pořadí se odmítnou. Když je vstup kratší, chybějící pozice se chovají jako chybějící položky struktury: dostanou výchozí hodnotu, nebo nahlásí chybu, pokud jsou required(). Metoda otherItems() dovolí, aby za definovanými pozicemi následovaly další položky, a určí schéma, kterému musí odpovídat, kdežto extend() přidá na konec další pozice.

Když se konfigurace skládají ve vrstvách, pozdější tuple nahradí ten dřívější celý, protože smíchat pozice dvou různých tuplů by dalo jen nesmysl.

Na rozdíl od struktury nepřijímá tuple null jako prázdnou hodnotu; pokud je null platnou hodnotou, zavolejte nullable().

Zastaralé položky: deprecated()

Když konfigurační volbu přejmenujete nebo zrušíte, ta stará může ještě chvíli fungovat a upozorňovat své uživatele. Označte ji pomocí deprecated([string $message]). Upozornění vrací $processor->getWarnings():

$schema = Expect::structure([
	'server' => Expect::string()->deprecated('The item %path% is deprecated, use host'),
	'host' => Expect::string(),
]);

$processor->process($schema, ['server' => 'localhost']); // OK
$processor->getWarnings(); // ["The item 'server' is deprecated, use host"]

Popis: description()

Každému prvku můžete přidat lidsky čitelný popis. Na validaci nemá vliv, jenže schéma bývá jediné místo, kde je význam konfigurační volby vůbec zapsaný, a description() vám ho dovolí napsat přímo tam. Popis se také propíše do exportu do JSON Schema:

$schema = Expect::structure([
	'host' => Expect::string('localhost')->description('Database server hostname'),
	'port' => Expect::int(3306)->description('Port the server listens on'),
])->description('Database connection');
verze: 2.x 1.x