Řešení zavináčů – nechť je z Latte základ Nette šablon
- jakubkulhan
- Člen | 55
Nikde jsem nenašel, jestli se hnulo s řešením
zavináčů,
takže tohle je
můj návrh řešení.
Základní předpoklad: pokud někdo používá Nette šablony, používá LatteFilter.
Bohužel z pozice filtru (resp. makroprocesoru) toho Latte moc
nezmůže.
Jednou možností by bylo z Latte udělat 2-pass
makroprocesor – tzn. že v prvním běhu by si zapamatoval potřebné
informace o šabloně
(kde je jaký snippet, jakými podmínkami je obalen) a podle toho by si
v druhém běhu
před dané podmínky zavináče „domyslel“.
Ale lepší by podle mě bylo, aby Latte povýšil z pozice filtru na
základní stavební
kámen Nette šablon – už ně makroprocesor, nýbrž
plnohodnotný jazyk.
Místo toho, aby přechroupával textovou verzi šablony, vytvořil by si
její in-memory
reprezentaci, tu zpracoval a z ní až vygeneroval PHP kód. Compile-time
šablon by se
asi prodloužil, run-time by byl stejně rychlý v případě zavedení
optimalizací by mohl
být ještě rychlejší.
Vytvořil jsem prototyp Latte
parseru v phpegu, napsat
plnohodnotný parser, ať v phpegu, nebo ručně, by nemělo být složité.
V prototypu
parseru je s HTML tagy pracováno jako s HTML tagy, takže
<li {if $first}class="first"{/fi}>
skončí chybou.
V plnohodnotném parseru by šlo
i toto řešit. Nicméně bych se přikláněl k něčemu na způsob mého
dřívějšího
smart_attrs
patche.
Aby z Latte byl opravdu jazyk, krom syntaxe by měl mít také jasně
definovanou
sémantiku. U většiny příkazů (podmínky, cykly, odkazy…) je to asi
jasné. Zaměřil
bych se na dědičnost.
Příkaz {extends …}
musí být v šabloně na prvním
místě, na jakémkoli jiném místě
znamená sémantickou chybu. Nechť vše vně bloků
(ať deklarovaných pomocí {block …}
, nebo atributu
n:block="…"
) je záporný
prostor a uvnitř bloků kladný prostor (terminologie vypůjčená z
Evoque). Extenduje-li šablona A
šablonu B,
záporný prostor A je nahrazen záporným prostorem B. Resp.
místo TOMu (Template Object Modelu) Ačka použijeme TOM Bčka,
a v něm nahradíme bloky shodnými bloky z Ačka.
Podobně by se měly všechny {include …}
uzly nahradit uzly
odpovídajícími
vkládanému obsahu.
Například TOM šalony:
{* A.latte *} some negative space {block foo}bar{/block} {block baz}bar{/block} negative space at the end of file
by byl:
[ @TextNode { text = "some negative space\n" }, @BlockNode { name = "foo", children = [ @TextNode { text = "bar" } ] }, @BlockNode { name = "baz", children = [ @TextNode { text = "bar" } ] } @TextNode { text = "negative space at the end of file\n" }, ]
a šablony:
{* B.latte *} {extends A.latte} {block foo}wow{/block}
by byl:
[ @TextNode { text = "some negative space\n" }, @BlockNode { name = "foo", children = [ @TextNode { text = "wow" } ] }, @BlockNode { name = "baz", children = [ @TextNode { text = "bar" } ] } @TextNode { text = "negative space at the end of file\n" }, ]
Až tedy proběhne fáze starající se v šablonách o dědičnost,
dostaneme in-memory
reprezentaci šablony (TOM), která bude obsahovat jak správný záporný
prostor, tak
správný kladný prostor a nebude obsahovat žádné includy. S tímhle TOMem
se už dají
provádět další transformace a kontroly.
Při generování se může používat technika jako doteď
(if (SnippetHelper::$outputAllowed) {
):
class LatteNode
{
abstract public function isAnySnippetInside();
abstract public function generatePhp();
}
class SnippetNode
{
public function isAnySnippetInside()
{
return TRUE;
}
public function generatePhp() { … }
}
class IfNode
{
/** @var string */
private $condition;
/** @var array of LatteNode */
private $ifClause;
/** @var array if LatteNode */
private $elseClause;
public function isAnySnippetInside()
{
foreach (array_merge($this->ifClause, $this->elseClause) as $node) {
if ($node->isAnySnippetInside()) {
return TRUE;
}
}
return FALSE;
}
public function generatePhp()
{
$php = "";
if ($this->isAnySnippetInside()) {
$php .= "}\n";
}
$php .= "if ({$this->condition}):\n"
if ($this->isAnySnippetInside()) {
$php .= "if (SnippetHelper::\$outputAllowed) {\n";
}
foreach ($this->ifClause as $node) {
$php .= $node->generatePhp();
}
if (!empty($this->elseClause)) {
if ($this->isAnySnippetInside()) {
$php .= "}\n";
}
$php .= "else:\n";
if ($this->isAnySnippetInside()) {
$php .= "if (SnippetHelper::\$outputAllowed) {\n";
}
foreach ($this->elseClause as $node) {
$php .= $node->generatePhp();
}
}
if ($this->isAnySnippetInside()) {
$php .= "}\n";
}
$php .= "endif;\n";
if ($this->isAnySnippetInside()) {
$php .= "if (SnippetHelper::\$outputAllowed) {\n";
}
}
}
- Honza Kuchař
- Člen | 1662
No což pak o to, Template bez Latte jsem nikdy nepoužil. (na to jsem příliš líný) Ale kdo to naimplementuje?
- jakubkulhan
- Člen | 55
Honza Kuchař napsal(a):
No což pak o to, Template bez Latte jsem nikdy nepoužil. (na to jsem příliš líný) Ale kdo to naimplementuje?
Já se na to klidně vrhnu. Z prototypu se k plnohodnotnému parseru dostanu za chvíli (pozor, selfpromo: díky skvělému phpegu :-)). Potřeboval bych vyjádření toho nepovolanějšího, jestli se to vůbec má šanci dostat do frameworku, nebo jestli zavináče začal už řešit jinak (protože na roadmapě 1.0 to je a zavázal se, že beta do 19. září vyjde…).
Mám další nápad k šablonám a jejich spolupráci se zbytkem frameworku.
Komponenty vykreslované v šablonách (formuláře např.) by mohly místo
$component->render()
vrátit Latte strom s
LatteNode
s, Latte by ho mohlo rovnou zkompilovat do šablony a
v dalších voláních by komponenta už jenom vracela data pro strom, který
dříve vrátila Latte (resp. zkompilovaný kód). Např. u formulářů je
stejně v plánu přepsat ConventionalRenderer
do syntaxe
LatteFilteru, tak by formuláře místo toho mohly vracet Latte strom.
- Honza Kuchař
- Člen | 1662
Zní to zajímavě, myslím, že minimálně myšlenka Davida zaujme a pokud jsi ochoten to implementovat, jistě nebude proti. Problém je čas, jako vždy. :)
- Dr.Diesel
- Člen | 53
jakubkulhan napsal(a):
Mám další nápad k šablonám a jejich spolupráci se zbytkem frameworku. Komponenty vykreslované v šablonách (formuláře např.) by mohly místo
$component->render()
vrátit Latte strom sLatteNode
s, Latte by ho mohlo rovnou zkompilovat do šablony a v dalších voláních by komponenta už jenom vracela data pro strom, který dříve vrátila Latte (resp. zkompilovaný kód). Např. u formulářů je stejně v plánu přepsatConventionalRenderer
do syntaxe LatteFilteru, tak by formuláře místo toho mohly vracet Latte strom.
U tohohle bych jen podotknul, ze si nejsem jisty, zda komponenta bude za vsech okolnosti pouzivat stejnou sablonu / podavat stejny vystup Latte stromu.
Editoval Dr.Diesel (23. 8. 2010 11:28)
- jakubkulhan
- Člen | 55
Honza Kuchař napsal(a):
Zní to zajímavě, myslím, že minimálně myšlenka Davida zaujme a pokud jsi ochoten to implementovat, jistě nebude proti. Problém je čas, jako vždy. :)
Čas neřešme, ten teď mám a věnuji ho tomu. Jen nechci, aby to dopadlo
jako u patche
pro atributy nebo FileJournal
u.
Dr.Diesel napsal(a):
U tohohle bych jen podotknul, ze si nejsem jisty, zda komponenta bude za vsech okolnosti pouzivat stejnou sablonu / podavat stejny vystup Latte stromu.
To si už komponenta musí rozhodnout sama, jestli se na stránce bude
vykreslovat podle podle doby dne, či podle počasí. Latte by měl zachovávat
kompatibilitu s LatteFilterem, takže by se ve výchozím stavu volalo
$component->render()
. Avšak pokud by komponenta implementovala
interface např. ILatteComponent
, jenž by obsahoval dvě
metody – getNode()
a getData()
--, v compile-time
by Latte nahradil widget
uzel stromem získaným voláním
getNode()
a v run-time by akorát stromu dodal data z metody
getData()
.
- David Grudl
- Nette Core | 8215
Tento týden jsem strávil pročítáním všech vláken na fóru a konečně se dostal k písmenu „R“ :-)
Jakube, především díky za RFC s návrhem rešení konkrétního problému, moc si toho vážím a doufám, že tím odstartuješ vlnu. Slibuju, že se těmto věcem budu primárně věnovat a zřídil jsem na to vlastní kategorii na fóru.
Než se dostaneme k technické stránce jeden dotaz: přijdeš zítra na Poslední sobotu? Pokud ano, bylo by asi lepší vše probrat osobně, než tu vypisovat elaboráty.
- jakubkulhan
- Člen | 55
David Grudl napsal(a):
Než se dostaneme k technické stránce jeden dotaz: přijdeš zítra na Poslední sobotu? Pokud ano, bylo by asi lepší vše probrat osobně, než tu vypisovat elaboráty.
Přijdu.
- jakubkulhan
- Člen | 55
Lopata napsal(a):
Jak dopadla diskuse na Poslední sobotě?
Jestli jsem to pochopil správně, zavináčová magie je potřeba pouze
v případě, že se používají staré snippety
{snippet nazevSnippetu}
, nové snippety (s dvojtečkou)
{snippet:nazevSnippetu}
jsou implementovány jinak (staré se
odchytávají přímo z vykreslované stránky /zavináče jsou tam k tomu,
aby se zbytečně nevykonával kód, který není potřeba vykonávat/ a nové
jsou samostatné funkce /šablona se tedy vůbec nevykonává, vykonávají se
pouze funkce reprezentující jednotlivé snippety; problém je s tím, že
tyhle snippety jsou bez scope, takže všechny proměnné v nich použité
musí pocházet přímo z inicializace šablony/).
Navíc kompilovaný parser, který vypadne třebas z phpegu, je velký. Pro syntaxi typu Latte bude přesahovat 50KiB. A aby tvořil parser šablon osminu velikosti frameworku (předpokládám, že velikost ostatního kódu kolem šablon by zůstala stejná /leč pravděpodobnější je, že kvůli práci se stromem, by velikost ostatku šablon vzrostla/)?
Takže v 1.0 bych počítal s rozloučením se se zavináči a starými snippety spíše než s přepisem LatteFilteru.
- David Grudl
- Nette Core | 8215
Abych to doplnil, snippety implementovány jsou, ale chování není dosud dotaženo. Jestli by bylo i tak užitečné přepsat LatteFilter do Jakubova phpegu je sporné, mimojiné proto, že je to odladěná věc a jsou tu jiné věci, které by bylo fajn udělat. Pokud se do toho ale bude chtít pustit, má samozřejmě ruce volné.