Symfony po krůčkách – Paralýza možností? OptionsResolver tě zachrání

Abychom podpořili klidnou vánoční náladu, vybrali jsme pro dnešní díl jednu z nejjednodušších Symfony komponent – OptionsResolver. Co to je? `Array_replace` na steroidech. Oproti němu má navíc pár užitečných metod – ty se postarají o to, aby hodnoty byly validní, a zajistí i normalizaci.
Seriál: Symfony po krůčkách (18 dílů)
- Symfony po krůčkách – Event Dispatcher 30. 11. 2015
- Symfony Console jako první rande se Symfony 7. 12. 2015
- Symfony po krůčkách – Filesystem a Finder 14. 12. 2015
- Symfony po krůčkách – Paralýza možností? OptionsResolver tě zachrání 21. 12. 2015
- Symfony po krůčkách – spouštíme procesy 4. 1. 2016
- Symfony po krůčkách – Translation – překlady jednoduše 11. 1. 2016
- Symfony po krůčkách – Validator (1) 18. 1. 2016
- Symfony po krůčkách – Validator (2) 25. 1. 2016
- Symfony po krůčkách – Routing 1. 2. 2016
- Symfony po krůčkách – MicroKernel 9. 2. 2016
- Konfigurujeme Symfony pomocí YAMLu 16. 2. 2016
- Symfony po krůčkách – oblékáme MicroKernel 23. 2. 2016
- Symfony po krůčkách – ClassLoader 29. 2. 2016
- Symfony po krůčkách – Twig 8. 3. 2016
- Symfony po krůčkách – Twig II. 15. 3. 2016
- Symfony po krůčkách – DomCrawler a CssSelector 23. 3. 2016
- Symfony po krůčkách – HTTP fundamentalista 12. 4. 2016
- Symfony po krůčkách – ušli jsme pořádný kus 19. 4. 2016
Nálepky:
Kdy se OptionsResolver hodí?
1. Jestli jsi někdy navrhoval REST API, možná znáš:
<?php
$options = [
'page' => isset($input['page']) ? $input['page'] : 1,
'type' => isset($input['type']) ? $input['type'] : 'product'
];
2. Nebo jsi dělal CLI aplikaci, která má mnoho vstupních hodnot – jako např. PHP_CodeSniffer:
$ php phpcs [-nwlsaepvi] [-d key[=value]] [--colors] [--no-colors] [--report=<report>] [--report-file=<reportFile>] [--report-<report>=<reportFile>] [--report-width=<reportWidth>] [--generator=<generator>] [--tab-width=<tabWidth>] [--severity=<severity>]...
3. A nebo modul se složitějším nastavením – jako např. rozšíření pro Doctrine:
// config.yml / config.neon
doctrine:
orm:
database: …
password: …
port: …
entity_manager:
one:
...
Co mají tyto příklady společného?
Mají mnoho vstupních hodnot:
- ty mohou nabývat různých stavů
- potřebují validaci typu (např. řetězec, číslo…,) nebo vůči seznamu hodnot (např. buď „ano“ nebo „ne“)
- vyžadují normalizaci, aby se s nimi dále pracovalo lépe (např. doplnění relativní cesty na absolutní)
- někdy mají defaultní hodnoty, jindy ne
Takový jednoduchý validátor jistě zvládne napsat každý. Jestli jsi ale v situaci, kdy s kódem pracuje více lidí, hodí se zvolit nějaké ověřené řešení. To by mělo mít dokumentaci a určité normy.
V těchto situacích ti přijde vhod OptionsResolver.
Jak vypadá použití v praxi?
Použití v praxi najdeš u ApiGenu. Pokud ho neznáš, Je to CLI aplikace, která generuje API dokumentaci zdrojového kódu.
Jako vstup má až přes 20 možností různých typů, mezi nimi pole, text nebo bool.
Každá nevalidní hodnota vede k ukončení běhu aplikace. S tím nám OptionsResolver pomůže. Jak? Mrkni na třídu ConfigurationOptionsResolver.
Krátký příklad
Vrátíme se k úvodnímu příkladu s REST API:
$options = [
'page' => isset($input['page']) ? $input['page'] : 1,
'type' => isset($input['type']) ? $input['type'] : 'clothes';
];
Na něm si ukážeme 3 hlavní featury této komponenty:
- nastavení defaultních hodnot
- validaci
- normalizaci
Nejdříve si komponentu nainstalujeme přes composer:
$ composer require symfony/options-resolver
Přidáme autoloader a vytvoříme novou instanci OptionsResolveru:
// index.php
require_once __DIR__.'/vendor/autoload.php';
// $input = …;
$optionsResolver = new Symfony\Component\OptionsResolver\OptionsResolver;
1. Nastavení defaultních hodnot
Výše uvedené použití isset()
nahradí metoda setDefaults()
:
$optionsResolver->setDefaults([
'page' => 1,
'type' => 'clothes'
]);
$options = $optionsResolver->resolve($input);
Co to udělá se vstupy?
$input = [];
// $options: 'page' => 1, type' => 'clothes'
$input = ['page' => 2];
// $options: 'page' => 2, 'type' => 'clothes'
$input = ['missing’ => 5];
// Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException
Přesně tak, OptionsResolver nám ošetří hodnoty, které nejsou povolené. To nám pomůže k lepší bezpečnosti aplikace.
Pro tip: Jako defaultní hodnotu můžeš použít i callback.
$optionsResolver->setDefault('page', function (Options $options) {
return ('clothes’ === $options['type']) ? 1 : 2;
});
Pro tip 2: A jestli chceš přidat možnost bez defaultní hodnoty, tak takhle:
$optionsResolver->setDefined('wrapper');
2. Validace
Někde nám chce někdo zadat data, o která zrovna nestojíme. Pojďme si je zvalidovat.
$optionsResolver->setAllowedTypes('page', 'int');
Můžeme přidat i více typů:
$optionsResolver->setAllowedTypes('page', ['int', 'float']);
Povolené hodnoty taxativně vyjmenovat:
$optionsResolver->setAllowedTypes('page', [1, 2, 3, 4, 5]);
Nebo stejné omezení jako výše, ale zapsané pomocí callbacku:
$optionsResolver->setAllowedTypes('page', function ($value) {
return $value >= 1 && $value <= 5;
));
3. Normalizace
Co to znamená? Normalizace je jakákoliv úprava dat po tom, co byla zvalidována. V našem případě zajistíme, aby se při vyžádání větší než povolené stránky zobrazila 10. strana.
$optionsResolver->setNormalizer('page', function ($options, $value) {
$maxPage = 10;
if ($value > 10) {
return $maxPage;
}
return $value;
});
A jsi zase o krok dál…
Dnes jsme si ukázali komponentu, která nám v chaosu mnoha možností pomůže udržet jasný směr.
Teď už víš, že OptionsResolver ti umožní:
- nastavit defaultní hodnoty
- validovat vstupy
- normalizovat hodnoty, pokud potřebují ještě nějaké modifikace
- a že se hodí při psaní složitější CLI aplikace, REST API nebo nějaké konfigurace
Chceš vědět víc?
Potřebuješ poznat OptionsResolver do hloubky? Mrkni na oficiální dokumentaci.
Příští pondělí si dáme vánoční volno a pak budeme v seriálu opět pokračovat.
Krásné Vánoce vám všem!
Tohle je dost dobrý. Určitě vyzkouším.
Super! Zajímal by mě, jak to dopadlo.
Použití téhle komponenty je hodně široký a hodí se znát praktické sitauce.
Díky za seriál o Symfony!
Ahoj, potěšení na naší straně :)
Už používáš nějaké komponenty, nebo jsi aspoň zkoušel?
Při použítí FOSRestBundle lze použít přes anotace obdobnou funcionalitu param fetcher – http://symfony.com/doc/current/bundles/FOSRestBundle/param_fetcher_listener.html
Díky za zmínku, ten neznám.
V čem se ve zkratce liší? Proč bys mu dal přednost?
… jsi opustil Nette a dal prednost Symfony? Nerypu, jen me zajimaji fakticke argumenty. Diky za odpoved.
To by bylo na článek samotný (byl by zájem?). Víceméně se shoduji s touto odpovědí.
Za sebe bych dodal: