Vyhodnocení Nette\DI\Statement v rozšíření

TomasG
Člen | 23
+
+1
-

Ahoj,
narážíme na problém, který se týká validace konfigurací v Nette rozšířeních.
Problém spočívá jednoduše v tom, že dané rozšíření validuje, jestli předaná hodnota je nějakého typu. Nicméně my potřebujeme danou hodnotu nějakým způsobem získat (například z ENVu), či upravit (například přetypovat) tak použijeme v neonu zápis pro funkci:

fooExtension:
    bar: ::getenv(BAR)

V tento moment nám aplikace při generování DI containeru spadne, pokud rozšíření foo obsahuje takovouto validaci:

class FooExtension extends CompilerExtension {
    public function loadConfiguration() {
        $config = $this->>getConfig();
        Validators::assertField($config, 'bar', 'string');
    }
}

… jelikož hodnota bar obsahuje instanci objektu Nette\DI\Statement . Nabízí se námitka, že ENV proměnné by se měly nasetovat jako parametry, respektive dynamické parametry v Configuratoru a následně je takto používat. Nicméně se nám zde stejně objevuje problém s přetypováním. Pokud by rozšíření validovalo hodnotu bar jako boolean , tak by při pouhém předání parametru aplikace znova spadla, jelikož ENV proměnné obsahují pouze stringy (0|1), takže je potřeba hodnotu přetypovat na boolean. Například takto:

fooExtension:
    bar: ::boolval(%env.BAR%)

A zde se opět dostáváme k tomu, že se nám do bar dostane instance Nette\DI\Statement . Jak řešit tento problém obecně, když spousta rozšíření jsou od třetí strany a obsahují tyto validace?

Nabízí se možnost “naučit” object Statement se vyhodnotit. Samozřejmě vyhodnotit by se dalo jen to, co je možné. Pokud by například Statement obsahoval volání nějaké služby, tak by to mělo vyhodit výjimku, protože není možné volat službu v compile time. A nějakým specifickým zápisem v neonu by se “řeklo”, že právě tento Statement se má po zkompilování rovnou vyhodnotit. Tedy například něco takového:

fooExtension:
    bar: ::boolval('1') # vytvoří Nette\DI\Statement
    baz: !::boolval('1') # vytvoří Nette\DI\Statement, který se ihned vyhodnotí a výsledek se použije jakožto hodnota

Samozřejmě ten zápis může být jiný, vykřičník jsem použil jen jako příklad. Teoreticky by to mohlo fungovat bez BC breaku, jelikož výchozí chování by bylo zachováno. Je toto dobrý/správný způsob, jak by se tento problém dal řešit, či se nabízí nějaký lepší a čistější způsob?

David Grudl
Nette Core | 8205
+
0
-

Řekl bych, že v reálu se tímto způsobem použijí asi jen funkce getenv, constant, defined a přetypování. Nebo ne?

Pokud by šlo o tyto funkce, klidně bych jim dal nativní obdoby, které by se vykonaly už při kompilaci. Něco jako je not().

Koneckonců zrovna funkce boolval, intval atd. nejsou úplně ideální, bylo by lepší mít jejich alternativy, které ověří, že hodnotu vůbec lze přetypovat (např. abc nebo 10.3 nejde na int, naopak 12 jde na int, atd.)

TomasG
Člen | 23
+
0
-

Většinou si asi člověk vystačí s těmito funkcemi a přetypováním. Ale napadá mě například použití, jaké máme my:

http:
	proxy: ::explode(',', ::getenv(TRUSTED_PROXIES))

Tady to zrovna hezky projde a ve výsledném Containeru je:

$service->setProxy(explode(',', getenv('TRUSTED_PROXIES')));

Ale kdyby tam někde byla nějaká validace, že se musí jednat o pole, či pole stringů, tak už to neprojde. Navíc my třeba nepoužíváme nativní getenv() funkci, ale vlastní, která to hledá a vrací z pole $_ENV. Při načítaní ENVů totiž nepoužíváme putenv().

Jinak mimo to, původně jsme se chtěli dostat k tomu, aby ve výsledném Containeru nebyly už finální hodnoty, ale aby tam bylo přímo volání getenv() (té naší) a i to přetypování etc.
Abychom mohli změnit ENV proměnné a nemazat cache Containeru… ale pak to zase padá na těch různých validacích napříč rozšířeními.

David Grudl
Nette Core | 8205
+
+4
-

Přidal jsem do masteru podporu pro bool(), int(), float(), string() a společně s not() se vykonají v compile-time, pokud je to možné.

David Grudl
Nette Core | 8205
+
0
-

TomasG napsal(a):

Většinou si asi člověk vystačí s těmito funkcemi a přetypováním. Ale napadá mě například použití, jaké máme my:

Ale kdyby tam někde byla nějaká validace, že se musí jednat o pole, či pole stringů, tak už to neprojde. Navíc my třeba nepoužíváme nativní getenv() funkci, ale vlastní, která to hledá a vrací z pole $_ENV. Při načítaní ENVů totiž nepoužíváme putenv().

Ale ona tam validace je. Pomocí schématu, konkrétně proxy je

Expect::anyOf(
	Expect::arrayOf('string'),
	Expect::string()->castTo('array')
)->default([])
->dynamic()

a právě to dynamic() říká, že to může být parametr vyhodnocený až za běhu a pak se také validuje. Zkus to udělat tímto způsobem, je to fakt lepší.

TomasG
Člen | 23
+
+2
-

David Grudl napsal(a):

Přidal jsem do masteru podporu pro bool(), int(), float(), string() a společně s not() se vykonají v compile-time, pokud je to možné.

Super, (y)

Aha, ono to totiž běží na Nette 2.4, ne 3, takže to co jsem psal nebylo možná úplně relevantní. Pokud mě tedy validace pustí pomocí dynamic() dál, když se bude jednat o Statement a ne žádný primitivní typ, tak potom není co řešit a vyřeší se to budoucím přechodem na Nette 3 :)
Díky moc!