RFC – konverze na datový typ enum a int při mapování hodnot formuláře

m.brecher
Generous Backer | 863
+
0
-

Ahoj !

Moderní datový typ enum je v PHP již nějaký ten rok a jeho podpora v Nette formulářích chybí – viz problém reportovaný zde: https://forum.nette.org/…na-enum-type

Nette formuláře umožňují jako druhý parametr handleru požádat o předání $values ve formátu vlastní datovou třídu (DTO). Pokud DTO obsahuje pole s typem Enum, vyhodí PHP TypeError, protože Nette neprovádí automatický převod. Tato chyba není zanedbatelná – znemožňuje použít mapování hodnot formuláře na třídu pokud chceme použít moderní typový přístup s enum.

Fix je jednoduchý – před předáním $values do handleru formuláře otestovat typy položek DTO a pokud je typ enum provést převést string na enum case. Je ale potřeba rozhodnout zda převádět na enum pomocí klíče name nebo value (backed enums). Jak backed enumy používáme? Typický backed enum vypadá takto:

enum Type: string
{
    case Legal = 'právnická osoba';
    case Physical = 'fyzická osoba';

    // data for Select control
    public  static function getNamesByValue(): array
    {
        return array_combine(
            array_column(self::cases(), 'name'),
            array_column(self::cases(), 'value'),
        );
    }
}

Obvykle používáme case (Type::Legal) typově v kódu, zatímco ve výstupu v šablonách použijeme human readable value (‚právnická osoba‘). Backed i nebacked enumy musíme proto převést pomocí klíče name.

V metodě Nette\Forms\Container::getUntrustedValues() v řádku $obj->$name = $value je přesně to místo, kde se předávají $values formuláře do datového objektu $obj, který je buďto vytvořeno z custom požadované třídy, nebo se použije stdClass. Takže stačí v předpřipraveném objektu $obj hledat enum property a provést převod $value na enum case.

Vyzkoušel jsem o odladil opravu kódu a přitom jsem zjistil, že Nette neprovádí žádnou automatickou konverzi podle datových typů polí (propert) v custom DTO třídě. Nette totiž spoléhá na to, že si sami ohlídáme, aby datový typ v DTO odpovídal datovému typu formulářového prvku. To může být někdy problém. Např. když si vytvoříme vlastní custom prvek pro přesně dvoumístný integer – to nejde pomocí addInteger(), tak potom z tohoto prvku získáme na výstupu integer jako string např. ‚33‘.

Pokud definujeme custom DTO třídu, je logické a očekávatelné, že Nette se pokusí string na int automaticky přetypovat a pokud to korektně nelze tak vyhodí výjimku – tj. ne jako vestavěné (int) převede ‚33q‘ na 33, ale v tomto případě vyhodí výjimku. Proto jsem přidal i automatický převod int polí.

PR – https://github.com/…rms/pull/337

Editoval m.brecher (3. 8. 23:13)

David Grudl
Nette Core | 8218
+
+2
-

Díky za PR. Já ty enumy shodou okolností řešil dnes odpoledne takže to mám taky hotové :-)

Ale to automatické přetypování tam dávat nechci, to jde řekl bych proti duchu striktního typování.

m.brecher
Generous Backer | 863
+
0
-

@DavidGrudl

Já to shodou okolností řešil dnes odpoledne takže to mám taky hotové :-)

Super !

Ale to automatické přetypování tam dávat nechci, to jde řekl bych proti duchu striktního typování.

Myslíš automatické přetypování na int ?? Tohle když tam nebude tak si v custom prvku musí každý sám zajistit správný typ na výstupu, což je hodně striktní, ale týká se to custom prvků a chápu koncepci, že formulářový prvek má mít na výstupu správně definovaný datový typ.

Ale enum pole myslím by se měly přetypovat automaticky a pokud to nejde tak vyhodit výjimku, nějak ručně to dělat mě přijde nepraktické. V Nette se používá automatický převod na deklarovaný datový typ na řadě míst – třeba zrovna formát hodnot formuláře – požádáš o array, ArrayHash, stdClass, nebo custom DTO třídu – a Nette to podle udaného typu natypuje automaticky. Select prvek má na výstupu array a udáním enum typu v příslušné property v DTO třídě dáváme předpis, že to má Nette natypovat jinak než je default výstupní typ selectu – je to analogické situaci, popsané v předchozí větě. U enumu bych si starosti s duchem striktního typování nedělal a díval bych se na to pragmaticky z pohledu snadné použitelnosti.

David Grudl
Nette Core | 8218
+
+2
-

Mapování na enumy mám hotové, už je to na GitHubu.

Naopak automatické přetypování stringů a intů je něco, pro co nevidím žádný důvod (ten příklad s custom prvkem jsem nepochopil), ba přímo to považuji za nežádoucí a vadilo by mi to.