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');