Latte 3: největší vývojový skok v dějinách Nette

David Grudl
Nette Core | 7737
+
+28
-

Prosím famfáry, na scénu přichází Latte 3. S kompletně přepsaným kompilátorem. Nová verze představuje největší vývojový skok, jaký kdy v Nette nastal.

Proč vlastně Latte

Latte má překvapivou historii. Původně totiž nebylo myšleno vážně. Mělo dokonce demonstrovat, že žádný šablonovací systém není v PHP potřeba. Bylo pevně spjato s presentery v Nette, kde však nebylo defaultně zapnuté a programátor jej musel aktivovat přes tehdejší ošklivý název CurlyBracketsFilter.

Zvrat přišel až s nápadem, že šablonovací systém by mohl HTML stránce rozumět. Vysvětlím. Pro ostatní šablonovací systémy je text v okolí značek jen šumem bez jakéhokoliv významu. Je jedno, jestli jde o HTML stránku, CSS styl nebo třeba text v Markdownu, šablonovací systémy vidí jen shluk bajtů. Latte naopak dokument chápe. Což přináší spoustu zásadních výhod. Od komfortu v podobě vychytávek jako jsou třeba n:attributy, až po ultimátní bezpečnost.

Latte tak ví, jakou použít escapovací funkci (což většina programátorů neví, ale díky Latte to nevadí a nevytvoří bezpečnostní díru Cross-site scripting). Zabrání vypsání řetězce, který by v určitém místě byl nebezpečný. Dokonce dokáže předejít dezinterpretaci mustache závorek frontendovým frameworkem. A bezpečnostní experti nebudou mít co žrát :)

Nečekal bych, že tímto nápadem přeběhne Latte ostatní systémy o 10 let, protože dodneška vím pouze o dvou, co takto fungují. Krom Latte jde ještě o Soy od Google. Latte a Soy jsou jediné opravdu bezpečné šablonovací systémy pro web.

Druhou klíčovou vlastností Latte je, že pro výrazy uvnitř značek (někdy se říká maker) používá jazyk PHP. Tedy syntaxi programátorovi důvěrně známou. Vývojář se tak nemusí učit nový jazyk. Nemusí zkoumat, jak se to či ono v Latte píše. Prostě to napíše tak jak umí. Naopak třeba populární šablonovací systém Twig používá syntaxi Pythonu, kde se i zcela základní konstrukce píší odlišně. Například foreach ($people as $person) se v Pythonu (a tedy i Twigu) píše jako for person in people, což zcela zbytečně nutí mozek přepínat mezi dvěma opačnými konvencemi.

Latte tedy má oproti konkurenci natolik podstatnou přidanou hodnotu, že má smysl investovat úsilí do jeho údržby a vývoje.

Současný kompilátor

Latte a jeho syntax vznikla před 14 lety (rok 2008), současný kompilátor o tři roky později. Uměl už tehdy vše podstatné, co se dodnes používá, tedy i bloky, dědičnost, snippety atd.

Kompilátor fungoval jako jednoprůchodový, po znamená, že parsoval šablonu a rovnou ji přetvářel do PHP kódu, který sestavil do výsledného souboru. Jazyk PHP používaný ve značkách (tj. v makrech) se tokenizoval a poté procházel několika procesy, které tokeny upravovaly. Jeden proces doplňoval řetězcové uvozovky kolem identifikátorů, jiný přidával syntaktické vychytávky, které PHP tehdy neznalo (například zápis polí pomocí [] místo array(), nullsafe operátory ?->) nebo které nezná doposud (zkrácený ternární operátor, filtry ($var|upper|truncate), atd).

Tyto procesy ale nijak nekontrolovaly PHP syntax nebo používané konstrukce. Což se výrazně změnilo až před dvěma lety (rok 2020) s příchodem sandbox režimu. Sandbox hledá v tokenech možné volání funkcí a metod a upravuje je, což není vůbec jednoduché. Přičemž případné selhání je vlastně bezpečností chybou.

Nový kompilátor

Za jedenáct let vývoje Latte se našly situace, kdy jednoprůchodový kompilátor nestačil (třeba při inkludování bloku, který ještě nebyl definován). Všechny issue šlo sice vyřešit, ale ideální by bylo přejít na dvoukrokovou kompilaci, tedy nejprve šablonu naparsovat do mezipodoby, do AST stromu, a pak teprve z něj vygenerovat kód třídy.

Taktéž z postupným vylepšováním PHPlike jazyka používaného ve značkách přestávala dostačovat reprezentace v tokenech a ideální by bylo jej také naparsovat do AST stromu. Naprogramovat sandbox nad AST stromem je výrazně snadnější a dá se garantovat, že bude skutečně neprůstřelný.

Trvalo mi pět let se do přepsání kompilátoru pustit, protože jsem věděl, že to bude extrémně náročné. Už samotná tokenizace šablony představuje výzvu, neboť musí běžet paralelně s parsováním. Parser totiž musí mít možnost ovlivňovat tokenizaci, když například narazí na n:syntax=off.

Podporu pro paralelní běh dvou kódů přináší až Fibers v PHP 8.1, nicméně Latte je zatím nevyužívá, aby mohlo fungovat na PHP 8.0. Místo toho používá obdobné coroutines (v dokumentaci PHP o nich nic nenajdete, tak alespoň odkaz na Generator RFC). Pod kapotou Latte se tedy odehrávají kouzla.

Nicméně jako ještě mnohem náročnější úkol mi připadalo napsat lexer a parser pro tak komplexní jazyk, jako je dialekt PHP používaný ve značkách. V podstatě to znamenalo vytvořit něco jako nikic/PHP-Parser pro Latte. A zároveň i nutnost formalizovat gramatiku tohoto jazyka.

Dnes můžu říct, že se mi povedlo všechno dokončit. Latte má kompilátor, jaký jsem si dlouhá léta přál. A z toho původního nezbyl ani jediný řádek kódu :-)

David Grudl
Nette Core | 7737
+
+17
-

Kompatibilita a Latte 2.11

Nové Latte má formálně přesně definovanou gramatiku, která by měla co nejvíce odpovídat tomu, co jsme si zvykli psát v Latte 2. Nový kompilátor by tak měl fungovat stejně jako předchozí, až na určité konstrukce, kde to buď není možné, nebo by to bylo extrémně komplikované. Proto vyšla přechodová verze Latte 2.11.

Tato verze není klasická 2.x verze Latte, ale slouží jako přechodová na Latte 3. Nepřináší nic nového, jen pomocí E_USER_DEPRECATED upozorňuje na vše, co v novém Latte nebude fungovat.

Změny v syntaxi (aktualizováno)

  • Ve filtrech se jako oddělovač parametrů používá čárka, dříve |filtr: arg : arg a nyní |filtr: arg, arg
  • Zrušení automatického rozlišování párového a nepárového {label foo} vs. {label foo}...{/label} (nepárové je potřeba psát {label /})
  • A také párový vs nepárový {_$var} vs {_}...{/}, druhou variantu nahrazuje nové {translate}...{/translate}
  • Chybějící dvojité uvozovky u výrazů jako {block foo-$var} je potřeba psát jako {block foo-{$var}} nebo {block "foo-$var"}
  • Značku {includeblock file.latte} nahrazuje {include file.latte with blocks}
  • Atributy n:inner-xxx, n:tag-xxx a n:ifcontent nelze použít u nepárových HTML elementů
  • Atribut n:inner-snippet musí být psán bez inner-
  • {include "abc"} by mělo být psáno jako {include file "abc"}, pokud „abc“ neobsahuje tečku a není tak jasné, že jde o soubor
  • |noescape musí být uveden jako poslední filtr
  • Je třeba dodržet velikost písmenek u filtrů (v Latte 3 bude case sensitive)
  • odstranění magické proměnné $iterations (nejde o $iterator!)
  • zrušení Engine::addFilter(null, …) ve prospěch addFilterLoader()
  • musí být ukončené značky </script> a </style>

Většina bodů jsou krajní případy, na které v šablonách nenarazíte. Ale na ty první asi jo. Pokusím se k tomu dát vysvětlení.

  • Dvojtečka jako oddělovač argumentů se stala problematická, protože koliduje s ternárním operátorem a není jasné, jestli |filter: $a ? $b : c znamená |filter: ($a ? $b) , $c nebo |filter: ($a ? $b : $c). A do toho ještě navíc přišly pojmenované argumenty |filter: arg: $val. Takže je už nějakou dobu deprecated, jen teď na to Latte upozorní notickou.
  • Automatické rozlišování párového a nepárového dělá problém v pluginu pro PhpStorm. Cca polovina uživatelů používá {label} lako párovou a polovina jako nepárovou značku. Nyní je nepárové použití potřeba zapsat jako {label .../}. Totéž se to týká i značky {_} pro překlad. Tady si myslím, že {_$var} je ideální pro nepárové použití, zatímco párové {_}text{/} vypadá blbě a nahrazuje ho {translate}text{/translate}.
  • ad {block foo-$var}: V Latte se běžně píšou řetězce bez uvozovek, například {include file.latte} atd. Přitom přísně vzato, tečka je operátor pro spojování a alfanumerické identifikátory se považují za řetězce, takže file.latte znamená 'file' . 'latte', tedy ve výsledku 'filelatte'. Latte 3 se snaží bezuvozovkové řetězce z velké míry podporovat, ale v některých případech, jako je právě foo-$var, už je bude chtít. Věřte, že to i přidá na srozumitelnosti, značka {block "foo-$var"} se čte líp.

Nebo lze doplnit složené závorky, tj. {block foo-{$var}} a pak uvozovky nejsou potřeba. Tohle se spíš využije v n:attributech, ve kterých se tedy místo `n:block="foo-$var" použije n:block="foo-{$var}".

Latte linter

Součástí Latte je i triviální lintovací nástroj, takže jestli jsou všechny šablony na disku OK by se mělo dat zjistit pomocí něj.

{php kod}

Dialekt PHP v Latte 3 podporuje pouze výrazy. To znamená, že uvnitř značky nemůžete napsat třeba konstrukce jako if…else apod. Ty ostatně ani nikdy nedávaly v žádné značce smysl, s výjimkou značky {php} resp. {do}.

V Latte 3 můžete v těchto značkách psát výrazy (i šílené, jako {do $a += match ($var) { 10 => $y } }), ale ne statementy ukončené středníkem.

Můžete však snadno aktivovat rozšíření RawPHP, které umožní ve značce {php} psát úplně cokoliv na zodpovědnost autora šablony.

$latte->addExtension(new Latte\Essential\RawPhpExtension);

Rozšíření pro Latte

S kompletním přepsáním parseru se zcela změnil i způsob psaní vlastních maker. Pokud máte pro Latte vytvořené vlastní značky, bude třeba je napsat znovu pro verzi 3. V tuto chvíli je nejlepším návodem se prostě podívat, jak jsou napsané výchozí značky.

Rozšíření pro Latte je standardně součástí balíčků nette/application, nette/caching a nette/forms. Ve všech jsem už přidal podporu pro Latte 3. Nachází se jak v masteru, tak i v 3.1-dev větvích. Nic by vám tedy nemělo bránit testování

Btw, značky pro překlad a snippety už nejsou přímo součástí Latte, ale jejich rozšíření v nette/application.

Testování

Ano, přichází na řadu testování. Třeba tento web na Latte 3 v pohodě běží. Ale je potřeba zjistit, co všechno neběží, aby se to mohlo vyřešit.

Pro testování stačí, když si nainstalujete Latte 3.0-dev a výše uvedené balíčky v 3.1-dev verzích (a máte PHP 8). A pokud si partu doplníte o Tracy 2.9.2, zjistíte, že umí zobrazovat nejen řádek kde je chyba v šabloně, ale nově i sloupec.

Můžete také nainstalovat v2.11 a ověřit, jestli vaše šablony jsou ready na trojku.

filbar
Člen | 12
+
0
-

Teď se dívám na https://github.com/…rmMacros.php a vidím tu starou syntax – bude teda možné používat i původní kód?

jiri.pudil
Nette Blogger | 1007
+
0
-

filbar napsal(a):

Teď se dívám na https://github.com/…rmMacros.php a vidím tu starou syntax – bude teda možné používat i původní kód?

Spíš bych si tipnul, že další nette balíčky budou minimálně nějakou dobu kompatibilní s oběma major verzemi Latte

filbar
Člen | 12
+
0
-

Když to zkoumám, tak to vypadá moc pěkně. Dívám se pro úvod na tento příklad – https://github.com/…FormNode.php a jestli to chápu dobře, tak create se volá jako úvod na začátku, print obsahuje ve volání return $context->format řetězec a další parametry cca jako ve sprintf? To co je pro mně ale záhadou je jaké parametry má obsahovat getIterator?

David Grudl
Nette Core | 7737
+
+12
-

Zkusím stručně popsat jak fungují rozšíření, jen upozorňuju, že jsme ve fázi beta a může docházet k nějakým změnám.

Rozšíření dědí od Extension a přidává se do Latte pomocí $engine->addExtension(...). Může implementovat tyto metody:

  • beforeCompile() je volaná na začátku kompilace, tedy když chceme kreslit šablonu co ještě není v cache. Dále se při kompilaci volá:
    • getTags() vrací asociativní pole název tagu ⇒ callback, který značku parsuje. Název může být i ‚n:abc‘, tedy n:attribut
    • getPasses() vrací asociativní pole pořadí ⇒ callback, který po dokončení parsování bude moci upravovat AST strom (tvoří jej tzv. nody)
  • beforeRender() se volá před každým vykreslením šablony a dále se volá
    • getFilters() a getFunctions() vrací asociativní pole název filtru/funkce ⇒ callback se všemi filtry/funkcemi

Pokud přidám víc rozšíření a ty mi vrátí v metodách getTags(), getFilters(), getFunctions() stejný klíč, vyhrává poslední přidané rozšíření. Jinými slovy, mé rozšíření může přepsat značku/filtr/funkci z defaultního CoreExtension.

Callback vrácený getTags() je funkce, která má za úkol zpracovat značku, tj. buď {foo ...} nebo n:foo="...". Což znamená, že naparsuje a zkontroluje argumenty a vrátí nějaký node (potomek Latte\Compiler\Node), který se stane součástí AST stromu. Jako příklad si můžeme ukázat funkci zpracovávající značku {do ...}. Jako parametr dostává objekt Tag, kde jsou všechny informace o tagu (jestli je to n:attribut nebo ne, na jakém je řádku, přístup k parseru argumentů apod.) a vrací objekt Latte\Essential\Nodes\DoNode. Připadalo mi elegantní tyhle funkce umístit přímo nodů, které vracejí, jakožto statické metody (tj. DoNode::create()), ale nemusí to tak být, viz funkce pro značky {l} a {r}.

Funkce obvykle volají metody $tag->expectArguments() (vyhoď výjimku, pokud značka nemá argumenty), $tag->parser->parseExpression() (naparsuj PHP výraz v argumentu), a $tag->parser->parseFilters() (naparsuj část s filtry neboli modifikátor).

Pokud je značka párová ({foo ...} ... {/foo}), tak funkce navíc udělá to, že předá zpět řízení parseru a nechá ho naparsovat celý vnitřek, který ji poté vrátí, a ona s ním nějak naloží, obvykle uloží do proměnné. Tohle dělá yield, viz třeba tady.

Takže výstupem CaptureNode::create() je objekt CaptureNode, který v property $variable obsahuje argument (jako objekt ExpressionNode), v property $filter použitý filter (jako objekt FilterNode nebo null) a v property $content vnitřní obsah (jako objekt AreaNode).

Všechno jsou to Node.

Když po skončení parsování přijde na řadu procházení a úpravy AST stromu (což dělají funkce vrácené z getPasses()), a narazí se na tento CaptureNode, ten řekne procházeči, že má pokračovat do hloubky do nodů uložených v properites $variable, $filter a $content. A právě to oznamuje funkce getIterator().

No a nakonec se celý node vykreslí do PHP podoby, což dělá funkce print(). K dispozici má objekt PrintContext, kde je užitečná metoda format(), něco jako printf, ale zaměřená pro účely generování PHP kódu.

Ještě se vrátím k tomu yield. Že je značka párová (tedy že funkce používá yield) není potřeba nikde uvádět, protože Latte si to zjistí z reflexe. Yield standardně naparsuje a vrátí celý obsah mezi otevírací a koncovou značkou, ale lze mu také předat jako argument seznam značek, na kterých se má zastavit. Tak třeba v případě {try} .A. {else} .B. {/try} ho necháme zastavit na {else} a vrátit jen část .A.. Krom ní vrací i zmíněný objekt Tag, popisovač značky, tentokrát pro značku {else} (v kódu TryNode se ukládá do proměnné $nextTag). Následně se yield volá ještě jednou, aby vrátilo část .B..

Pokud by tam {else} nebylo, vrátí první yield celý vnitřek a $nextTag bude obsahovat objekt Tag pro koncovou značku {/try}. Nebo taky null, pokud {/try} neexistuje, třeba když je použito jako atribut n:try="...".


Pozn: chování je vlastně evolucí Latte v2. Hlavní rozdíl je doplnění mezikroku, kdy funkce parsující tag vrací objekt node a až ten vykresluje PHP kód v print(). A také v použití yield, které v době vzniku Latte 2 neexistovalo a díky kterému není potřeba parsující funkci rozdělovat na dvě, jednu pro otevírací značku a jednu pro uzavírací.

dakur
Člen | 337
+
+3
-

Wow, super! Těším se na nový zápis vlastních maker. 👍 Tedy extensions..

filbar
Člen | 12
+
0
-

Ještě mě napadnul jeden dotaz – věci jako {nl2br($value->custom_value)} v nové implementaci budou fungovat, nebo nee?

David Grudl
Nette Core | 7737
+
0
-

Ne, od toho je |breakLines

mkoula
Člen | 50
+
+1
-

Čte Latte Linter latte-lint konfiguraci projektu a umí rozpoznat vlastní makra? Nebo je nějaká možnost to do linteru dostat?

❯ vendor/bin/latte-lint

Latte linter
------------
Usage: latte-lint <path>

V rámci ClI není žádný help, ani konfigurace. Našlo mi to nějaké chyby, což je přínosné, ale zároveň to vyhodí chybu ala:
Unknown tag {stars} (on line 117)
a najít pak ty skutečné chyby od chybějících vlastních maker je v tom výstupu ne zrovna čitelné…

mkoula
Člen | 50
+
0
-

David Grudl napsal(a):

Ne, od toho je |breakLines

Píšeš o tom, že nová verze bude case sensitive a zde je breakLines. Tak jsem si vzpomněl, že zrovna včera jsem to používal a PHPStorm a jeho Latte plugin (asi pro @JanTvrdík) nabízí v rámci našeptávání jen breaklines. A fakt se mi to nechce zpětně ručně dopřepisovat :-)

Tak nevím jestli tohle bude problém s novou verzí nebo ne?

Martin Dřímal
Člen | 4
+
+2
-

mkoula napsal(a):
Tak nevím jestli tohle bude problém s novou verzí nebo ne?

V CoreExtension se registrujou obě varianty – https://github.com/…xtension.php#L103

David Grudl
Nette Core | 7737
+
+3
-

Čte Latte Linter latte-lint konfiguraci projektu a umí rozpoznat vlastní makra? Nebo je nějaká možnost to do linteru dostat?

V tuto chvíli jenom tak, že si instalaci rozšíření do kódu sám doplníš sem https://github.com/…n/latte-lint#…

Píšeš o tom, že nová verze bude case sensitive a zde je breakLines…

Mělo by fungovat obojí, stejně jako stripHtml a striphtml a nějaké další.

David Grudl
Nette Core | 7737
+
0
-

K té značce {label /} bych rád udělal ještě průzkum, jak často se používá jako párová a nepárová. Měl by to spočítat tento skript https://gist.github.com/…de700d0b721f. Zkuste tím prosím projít své šablony.

Můžete zahlasovat i tady https://twitter.com/…415332519945

Pokud by se ukázalo, že většinové použití je jako nepárová, tak bych našel pro Latte 3 řešení, aby se nemuselo psát {label /}, a naopak bych vytvořil novou značku pro párový label.

mskocik
Člen | 9
+
+3
-

David Grudl napsal(a):
Pokud by se ukázalo, že většinové použití je jako nepárová, tak bych našel pro Latte 3 řešení, aby se nemuselo psát {label /}, a naopak bych vytvořil novou značku pro párový label.

Parovy label som pouzil asi v jednom forme kedysi davno a ani som sa s tym nestretol na inych projektoch. Na druhu stranu upravit {label} na {label /} bola rychlovka pomocou regex search & replace vo VS code.

Martin Dřímal
Člen | 4
+
+1
-

Ty jo, celkem mě v anketě na twitteru překvapuje rozsáhlé používání nepárové varianty.. :) Nemám jediné nepárové použití, mám zažitý klasický label jako povinně párový tag a dodnes mě ani nenapadlo zkoušet to nepárově :D Mám pak třeba i css selectory navěšené na nějaký input uvnitř konkrétního labelu, atd..

Marek Bartoš
Nette Blogger | 651
+
0
-

@DavidGrudl Když už děláš ty velké změny, bylo by možné řešit i tenhle edge-case?

Před názvem bloku je občas třeba zapsat hashtag. Bude ještě třeba nebo je čas označit ho za deprecated? Bez předešlé znalosti bych čekal, že se dvojité uvozovky budou chovat jako string interpolation v php.

{ifset #"col-{$column->name}"}
	{include #"col-{$column->name}", row: $row, cell: $cell, iterator: $iterator}

Editoval Marek Bartoš (7. 4. 16:10)

David Grudl
Nette Core | 7737
+
+1
-

Použij {ifset block ...} a {include block ...}

Dvojité uvozovky se chovají jako string interpolation v php.

filbar
Člen | 12
+
0
-

Ještě jsem narazil, že mi přestalo fungovat

<option value="{plink this,(expand)$presenter->getParameters()}" {if $presenter->getParameter('ordering')==''}selected="selected"{/if}>{_ "GLOBAL_DEFAULT"}</option>

Dostávám hlášku – Cannot unpack array with string keys

Siki
Člen | 8
+
+1
-

Používám Latte 2.11.1

Mám následující zápis (funkční ve starších verzích Latte):

<div n:snippet="row-{$row->id}">

Nyní dostávám Deprecated hlášku: The expression ‘row-{$row->id}’ should be put in double quotes.

Když jsem přidal uvozovky, tak vyskakuje Warning (Use of undefined constant ‘row’ (this will throw an Error in a future version of PHP)):

<div n:snippet='row-"{$row->id}"'>

Jaké je řešení?

David Grudl
Nette Core | 7737
+
0
-
<div n:snippet='"row-{$row->id}"'>
Polki
Člen | 552
+
+9
-

David Grudl napsal(a):

<div n:snippet='"row-{$row->id}"'>

ugly

Kamil Valenta
Člen | 546
+
+4
-

A co je vlastně na výrazu

<div n:snippet="row-{$row->id}">

špatně? Vůbec by mě nenapadlo tam ještě něco přidávat, když latte vychovává člověka, aby makra ničím dalším neobaloval.
Pak také nevím, zda jsem sám, komu hláška připadá zavádějící, protože já už v prvním zápisu dvojité uvozovky vidím.
Výsledná konstrukce by mne nenapadla a ani když už ji vidím, si ji nedokážu nijak zdůvodnit…

Pavel Janda
Člen | 965
+
0
-

Souhlasím s oběma komentáři výše.

Latte oplývá (dneska by se řeklo umělou) inteligencí pro rozpoznání kontextu – což je skvělý! Ale – takto to působí, že pro jistotu musím obalit vypisovaný řetězec několika typy uvozovek, aby to bylo jó-safe..

Pokud se to týká pouze maker snippet, tak to jde asi překousnout, ale nevypadá to zkrátka moc inteligentně.

Editoval Pavel Janda (11. 4. 12:28)

Jan Tvrdík
Nette guru | 2590
+
+3
-

Alternativě by mi přišlo intuitivní i

<div n:snippet={"row-$row->id"}>

resp.

<div n:snippet={"row-{$row->id}"}>
David Grudl
Nette Core | 7737
+
+8
-

Hele mně se to taky nelíbí, takže to mi psát nemusíte, smysl má zkusit navrhnout konstruktivní řešení.

A co je vlastně na výrazu <div n:snippet="row-{$row->id}"> špatně? Odpověď je tady. Naopak korektní varianty jsou tyto.

Latte 2 se tak ňák dokázalo vypořádat s různými zápisy. Jenže dnes už není otázkou jen jedné knihovny – každou výjimkou komplikuju život třeba @mesour, který programuje plugin. Zároveň sandbox režim mění pravidla hry, protože nejisté chování může vést k chybám, které jsou najednou považovány za bezpečnostní. Takže se snažím jazyk co nejvíc zbavit výjimek a formalizovat. Stejně jako v PHP nemůžete jen tak vynechat dvojité uvozovky a doufat, že to kompilátor nějak zvládne, nejde to ani ve formálně přísném Latte 3.

Tedy je potřeba určit jasná pravidla. Například zda má chápat 123-$a jako 123 - $a nebo jako "123-$a"? A co třeba M_PI-$a, je to M_PI - $a nebo "M_PI-$a"? A co $a-123? Nebo foo.$b, nebo foo.{$b}, atd… Dřívější pravidlo „chápej vše do první mezery nebo čárky jako string“ má taky svá úskalí.

Ty pravidla je stále prostor upravit. Technicky jde o funkci parseUnquotedString(), můžete si s ní klidně pohrát.

Osobně se víc kloním k tomu co nejvíce eliminovat magii. Jak jsem psal, {block "foo-$var"} se mi jeví srozumitelnější než {block foo-$var}, protože chování interpolation v PHP řetězcích znám a vím, co si v nich můžu dovolit, zatímco bez těch uvozovek je to tricky. Bohužel ale v n:atributech to vede k nehezkému zdvojení uvozovek. Jenže zároveň to musí fungovat stejně, ať už je značka zapsaná jako {...} nebo n:xxx="...", protože jinak by šlo o výjimku dosti kolosální. …nebo ne? ;-)

Pavel Janda
Člen | 965
+
+1
-

@DavidGrudl Zkusíme se nad tím zamyslet. To víš, dal jsi nám automatickou převodovku a teď chceš, abychom řadili ručně. :D

David Grudl
Nette Core | 7737
+
+4
-

Zkusím udělat tuhle úpravu: jelikož v PHP nelze uvnitř výrazů používat složené závorky { }, tak vlastně zápisy jako xxx-{$x}, {$x}.xxx apod jsou vždy nevalidní (teda krom $foo->{$bar}) . Tedy by je šlo bezpečně považovat za výrazy, které mají být chápány jako unquoted strings, tedy řetězce.

Dál by tedy platilo, že:

  • foo-$var MUSÍ být v uvozovkách jako "foo-$var" (btw $var-foo muselo být v uvozovkách vždy)
  • foo-{$var} nebo {$var}-foo NEMUSÍ být v uvozovkách

Krom písmen a číslic by unquoted strings mohly obsahovat znaky _ - . / @ ~.

Marek Bartoš
Nette Blogger | 651
+
0
-

Super nápad. Jediná výjimka kdy složené závorky ve výrazu použít lze je v php 7.4 deprecated a v 8.0 removed offset access https://3v4l.org/58Rh8

Kamil Valenta
Člen | 546
+
0
-

David Grudl napsal(a):

A co je vlastně na výrazu <div n:snippet="row-{$row->id}"> špatně? Odpověď je tady. Naopak korektní varianty jsou tyto.

Mně to jako odpověď nepřipadá. Pokud to row- začíná uvozovkami, tak je jasně řečeno, že to má být string. Proč by se mělo dál row- vyhodnocovat v PHP?
Pokud napíšu <input size=„50“ n:name=„name“>, taky to nezhučí na tom, že by se vyhodnocovalo

<?php
name;

Kdy se jedná o pomlčku a kdy o mínus mi zase řekne kontext těch složených závorek, podobně jako třeba v bashi.

n:snippet="row-{$id}" // => "row-1"
n:snippet="{10-$id}" //  => "9"
David Grudl
Nette Core | 7737
+
+1
-

Uvozovky nejsou součástí hodnoty atributu.

Kamil Valenta
Člen | 546
+
+1
-

Nejsou.

$id = 1;
$foo = 'bar';

n:snippet="row" // => {snippet row}
n:snippet="row-{$id}" // => {snippet row-1}
n:snippet="row-{10-$id}" // => {snippet row-9}
n:snippet="row-10-{$id}" // => {snippet row-10-1}
n:snippet="{$foo}" // => {snippet bar}

Připadá mi, že to pokrývá všechny ty nejasné případy, které jsi nastínil. A je to pokryto jediným pravidlem, nemusím si pamatovat žádné „nebo“, kdy se to má či nemá obalovat uvozovkama. A v neposlední řadě to vychází ze zažité syntaxe $obj->{$var};

Marek Bartoš
Nette Blogger | 651
+
0
-

@KamilValenta Však David psal, že nynější řešení mělo situace, kdy nebylo jasné, jak se kód interpretuje, a tak se vynutilo zdvojení, a tady pak napsal, že je to řešitelné i pomocí složených závorek, tak kde vidíš problém?

Kamil Valenta
Člen | 546
+
+1
-

@MarekBartoš já nevidím problém nikde.
Reagoval jsem na to, že ‚„row-{$id}“‘ není úplně šťastné, David vyzval k diskuzi „Hele mně se to taky nelíbí, takže to mi psát nemusíte, smysl má zkusit navrhnout konstruktivní řešení.“
Ano, sám zmiňuje složené závorky, ale navrhuje řešení, které nestojí na jednotném pravidle, což podle mě snižuje použitelnost.

Moc nechápu, co jsi chtěl svým příspěvkem sdělit.

David Grudl
Nette Core | 7737
+
0
-

@KamilValenta to, co se píše do {...} a n:attr="...", je PHP kód. S určitými, ale pevně danými odlišnostmi, jako třeba že lze vynechávat uvozovky kolem „alfanumerickýchpomlčkových“ identifikátorů, používat zkrácený ternární operátor atd.

Ptáš se, co je špatně na <div n:snippet="row-{$row->id}">. Špatně je to, že row-{$row->id} není jednak validní výraz, ani výraz, který chceš zapsat. S uplatněním zmíněných pravidel jde o matematickou operaci 'row' - {$row->id}, ale ty chceš napsat řetězec "row-{$row->id}". Latte tyhle speciální případy, kdy chybí dvojité uvozovky, chápe, ale jsou to všechno výjimky, viz třeba jak se chápe file.latte v různých místech šablony https://fiddle.nette.org/latte/#….

Eda
Backer | 219
+
+1
-

Cením snahu to celé nějak sjednotit, zpřehlednit a zformalizovat :-)

Ale psát tady ty dvojité uvozovky mi tedy taky přijde dost neintuitivní.

filbar
Člen | 12
+
0
-

A co to dát do závorek celé?

n:snippet="{row-{$id}}"

případně

n:snippet="{row-$id}"

Za mě mi to přijde nejpřehlednější než míchání uvozovek, kde se člověk sdnadno přehlédne?

kukulich
Člen | 55
+
+3
-

Ok, tak mám za sebou tři dny přepisování všech maker Slevomatu na nový zápis. Je teda pravda, že jsem předtím už nějaký čas strávil řešením deprecated věcí z 2.11.0 a 2.11.1.

Plusy:

  • Rozhodně je to přehlednější. Dost pomohlo, že výstupem je teď PHP kód.
  • Lépe se to hackuje :) Např. je skvělé, že se snadno dají přepsat originální makra.
  • Je super, že je to striktnější. Našlo to spoustu i malých překlepů v šablonách.

Minusy:

  • Chvíli mi trvalo přijít na to, kterou metodu $tag->parser kdy použít.
  • Stejně tak, kdy použít %raw, %dump či %args.
  • Bojoval jsem s některými zápisy, ale věřím, že budoucí parseUnquotedString() to ještě vylepší, viz https://forum.nette.org/…jinach-nette#…

Počítám, že některé věci pořeší budoucí dokumentace či ještě úpravy před releasem, takže celkově je to za mne skvělý a už se těším, až to budu moct nasadit :)

Kamil Valenta
Člen | 546
+
0
-

David Grudl napsal(a):

@KamilValenta to, co se píše do {...} a n:attr="...", je PHP kód. S určitými, ale pevně danými odlišnostmi…

Vycházím z toho, že to až tak úplně PHP kód není. Nebo tedy že je těch odlišností celkem dost. Prostě jsou makra, která se vnitřně vyhodnocují různě.
Jak už jsem uvedl,

n:name="nazev"

se také nijak nevyhodnocuje a chápe se dle kontextu jako string,

<input type="text" n:attr="value: $item->getValue()">

se také nevyhodnotí „v PHP“, ale transformuje na value=„…“

Ale v zásadě je mi to jedno, zvyknu si na uvozovky v apostrofech, ač se to nejen mně zdá jako podivný krok někam stranou…
Už to dál rozvíjet nebudu, byl to jen návrh, který mi přišel přehledný.

David Grudl
Nette Core | 7737
+
+2
-

@KamilValenta Točíme se v kruhu a plevelíme tím vlákno, souhlas, ukončíme to. Každopádně uvozovky v apostrofech nebudou, viz předchozí posty.

David Grudl
Nette Core | 7737
+
0
-

@kukulich Tyjo, je to super, že to tak testuješ! Trošku se bojím, ať další bety nic nerozbijou. Dokumentaci dám na web až bude blížit do finále.

Ages
Člen | 119
+
0
-

Ahoj, aktualizoval jsem projekt, na Latte v2.11.0 a odstranil všechny deprecated věci.
Nicméně projekt běží na Nittru a spolu s verzí 2.11.1 jsem narazil na problém, že v názvu marka nově nemůže být tečka.
Tato změna, nevyvolá deprecated hlášku, ale shodí celou aplikaci díky LogicException Invalid tag name

Marek Bartoš
Nette Blogger | 651
+
0
-

@Ages 2.11.2 bude fungovat. https://github.com/…85b56830c556

Ages
Člen | 119
+
0
-

@MarekBartoš ok – 2.11.1 tedy přeskočím a počkám na vydání nové verze.

kukulich
Člen | 55
+
0
-

@DavidGrudl Testuju právě 2.11.2 a dostávám hlášku:

Expression '/images/fortunewheel/banner/fortunewheel.jpg?v3' should be wrapped in double quotes.

Je možné otazník přidat do podporovaných znaků, nebo to technicky nejde?

David Grudl
Nette Core | 7737
+
+1
-

No jde o to rozlišit, jestli určitý „shluk znaků“ považovat za unquoted řetězec nebo PHP výraz. Zkusím rozebrat všechny znaky, jestli mohou být v unquoted řetězci, u řady fakt váhám:

  • znaky a-z0-9_ mohou být součástí takového řetězce (a whitespace být nemůže)
  • $ nemůže, protože $foo chceme vnímat jako proměnnou, ale lze jej použít uvnitř složených závorek {$foo}
  • {} tím pádem můžou, ovšem fungují jako string interpolation
  • () nemůžou, protože znaky foo() chceme vnímat jako volání
  • [] nemůžou, protože znaky [x] chceme vnímat jako pole
  • : zápis class::const chceme vnímat jako výraz, ale pokud bude dvojtečka jen jedna, musí být povolená, protože se používá v odkazech
  • '" uvozují řetězec, nemůžou být součástí unquoted řetězce
  • # je v PHP komentář, ale ne v Latte, tak klidně může být v řetězci
  • , nemůže, protože v {include xxxx,yyy} ji vnímáme jako oddělovač
  • | nemůže, protože v {include xxxx|yyy} chceme vnímat | jako filtr

A od této chvíle to přestává být jednoznačné.

  • +-*/%.^ jsou operátory. Otázka je, zda chceme 25-13 vnímat jako výraz. V tuto chvíli jsou v řetězci -/. povolené, protože se běžné používají v CSS třídách a souborových cestách, tudíž 25-13 není výraz ale řetězec. Nevím, zda +*%^ povolit taky.
  • !~ jsou unární operátory. Chceme !true nebo ~123 vnímat jako výraz? Nevím…
  • <=> chceme vnímat 10<=20 jako výraz? To sice nedává moc smysl, ale 1>>3 už jako výraz smysl dává a <<< je začátek phpdoc syntaxe. Tudíž se kloním k tomu, aby v řetězci být nemohly
  • ? chceme vnímat true?a:b jako výraz? nevím…
  • &@;\ nenapadá mě kolidující případ pro tyto znaky, asi by mohly být v řetězci

Samozřejmě kombinací znaků vznikají další operátory, jako třeba -> nebo +=, ale tím že není povolený $, tak nevytvářejí kolize.

hrach
Člen | 1822
+
+1
-
  • 25-13, 1>>3, true?a:b vnimam jako retezec, vyrazy by u me „musely“ obsahovat mezeru.
  • !true a ~123 moc nevim – jaky je workaround, kdyby toto byly retezce?
David Grudl
Nette Core | 7737
+
0
-

Workaround kdyby to byly řetězce? Tomu moc nerozumím. Řetězce se dají vždycky zapsat do uvozovek. Naopak kdyby to řetězce nebyly, tak by se tam asi musely dát mezery.

David Grudl
Nette Core | 7737
+
+9
-

Latte 3 beta 2

Dnes jsem vypustit druhou beta verzi Latte. Hlavní úpravou je to, co se probíralo tady ve vlákně, a to parsování tzv. unquoted řetězců. V nich je nyní možné používat výrazy ve složených závorkách, takže např. row-{$foo} není potřeba dávat do uvozovek, což se ocení hlavně u n:atributů. Takže by parsování mělo být daleko bližší Latte 2. Ještě zbývá rozhodnout drobnosti viz tady.

Zároveň jsem zaktualizoval nette/forms, nette/caching a nette/application. Pro testování stačí do composer.json dát (ano, použijte dev, protože postupně opravuju nalezené chyby)

	"require": {
		"nette/application": "^3.1-dev",
		"nette/caching": "^3.1-dev",
		"nette/forms": "^3.1-dev",
		"latte/latte": "^3.0-dev",

Interní změny

Pak jsou tu nějaké interní změny a zajímají tedy jen ty, kdo už zkoušeli psát tagy pro Latte 3:

  1. změna TagParser::parseFilters(): ?Expression\FilterNodeparseModifier(): Node\ModifierNode
  2. změna TagParser::parseWithModifier()tryConsumeModifier()
  3. v Node změna property int $startLineLatte\Compiler\Position $position

ad 1) filtry v tagu se parsují do nového (non-nullable) objektu ModifierNode, jelikož původní implementace se mi nakonec nelíbila
ad 2) jako důsledek změn parsování unquoted řetězců
ad 3) pro předávaní čísel řádků v kódu jsem vytvořil třídu Position, která navíc nese informaci o sloupci, který se pak předává do chybových hlášek a Tracy od verze 2.9.2 jej vyznačuje v bluescreenu.

Příklad kódu, kde se projevily všechny tři změny, je třeba soubor IncludeFileNode.php. Ve většině kódu stačilo zaměnit ->startLine za ->position.

Kdybyste potřebovali cokoliv rozepsat víc, napište.

A pak už jdeme do finále

Předpokládám, že tohle by mohla být poslední beta a na API už sahat nechci. Takže by mohl brzy následovat RC a pak stable.

(Tento celý web včetně fóra už na Latte 3 jede)