[RFC] {foreach} se scope-safe proměnnými

David Grudl
Nette Core | 8247
+
+3
-

Ahoj, mám návrh na vylepšení chování {foreach} cyklu v Latte, které by mohlo zpříjemnit život mnoha vývojářům.

Aktuálně když napíšeme:

{var $id = main-content}

{foreach $products as $id => $product}
    ...
{/foreach}

{* Zde chceme použít původní $id nebo $product, ale jsou přepsané z cyklu *}
<div id="{$id}">  {* Mělo by být 'main-content', ale je tam poslední ID z cyklu *}
</div>

Latte už moře let vyhazuje varování, pokud {foreach} přepisuje globální proměnné (ty předané z presenteru). To je skvělé. Nicméně lokální proměnné jsou stále tiše přepisovány, což může vést k těžko odhalitelným bugům.

Navrhuji rozšířit toto chování tak, aby {foreach} vytvářel nový lokální scope pro své iterační proměnné. Tedy proměnné definované před cyklem by si zachovaly svou původní hodnotu i po jeho ukončení. Iterační proměnné by existovaly pouze uvnitř cyklu.

Tady ale narážíme na otázku zpětné kompatibility. U globálních proměnných (předaných z presenteru) je situace jednoduchá – jelikož Latte při jejich přepsání vyhazuje chybu, nikdo na toto chování nespoléhá a změna je bezpečná.

Jiná situace je u lokálních proměnných, které mohou vznikat různými způsoby:

  • deklarací pomocí {var} ne v {do}
  • jako parametry {define} bloků
  • v nadřazených foreach cyklech

Tyto proměnné jsou dosud tiše přepisovány a může existovat kód, který na tomto chování staví. Například:

{var $product = null}
{foreach $products as $id => $product}
    ...
{/foreach}

{* Zde využíváme, že $id a $product obsahují poslední aktivní položku *}
{if $product}
    Poslední aktivní produkt: {$product->name}
{/if}

Tento pattern se občas používá, ale lze ho přepsat pomocí pomocné proměnné:

{var $lastProduct = null}
{foreach $products as $id => $product}
    {var $lastProduct = $product}
{/foreach}

{if $lastProduct}
    Poslední aktivní produkt: {$lastProduct->name}
{/if}

Navíc v praxi často spíše než poslední hodnotu potřebujeme vědět, zda cyklus (ne)proběhl. Na to je mnohem vhodnější využít {else} větev cyklu {foreach}:

{foreach $products as $id => $product}
	...
{else}
    Nebyl nalezen žádný produkt
{/foreach}

Rád bych proto otevřel diskuzi, jak tuto změnu bezpečně zavést. Napadají mě tyto možnosti:

  1. Přidat novou variantu syntaxe pro scope-safe foreach
  2. Přidat runtime přepínač a povolit původní chování
  3. Označit změnu chování jako BC break pro příští major verzi

Co si o tom myslíte? Jaké máte zkušenosti s podobným použitím foreach? Setkali jste se s kódem, který by na tomto chování závisel?

Václav Pávek
Backer | 101
+
+1
-

Možná bych se zamyslel, zda těch scope-safe maker může být více. Pak bych volil prefix s takže by výsledek vypadal sforeach (safe foreach), sblock (safe block) apod. Po nějaké době a verzích by se původní definice staly safe (alias).

Runtime konfigurace by asi nebyla špatná, ale muselo by se zapínat nové chování kvůli zpětné kompatibilitě, obráceně je to defacto BC break.

Osobně bych šel do BC breaku / scope-safe.

Editoval Václav Pávek (16. 1. 15:20)

Kamil Valenta
Člen | 828
+
+2
-

Václav Pávek napsal(a):

sforeach (safe foreach), sblock (safe block)

Koncept, kdy něco v latte není ve výchozím stavu safe a člověk si o to musí říct, je krok zpět. A je to velká nevýhoda ostatních šablonovacích jazyků.

Osobně bych šel do BC breaku

Souhlas. Tipuji, že tento BCB moc bolet nebude. Prošel jsem desítky projektů z posledních let a use case, že by se sahalo na proměnnou ovlivněnou iteracemi cyklu, bylo nula…

Kevas
Člen | 13
+
0
-

Mám to podobně jak píše Kamil. Na žádném projektu nemám tento use case. Za mě teda taky BCB.

MajklNajt
Člen | 504
+
0
-

Narazil som iba na prípady, kedy ma ten aktuálny stav vysieral, takže som tiež za scope-safe a BC break

Šaman
Člen | 2667
+
0
-

Nejsem si jistý, jestli někdy nepoužívám hodnotu, která zbyde z foreache, v php kódu.
Jsem si ale skoro jistý, že ji nepoužívám v latte. Imho ten případný BC break moc bolet nebude.

ViPEr*CZ*
Člen | 821
+
+2
-

Mně na těchto změnách osobně vadí, že se to chová jinak než v samotném jazyku.
Syntax je skvělá (přesně jako v jazyce), jen kolem dám závorky {} a nemusím mít další know-how (a samozřejmě uzavírací makro).

Samozřejmě kdo to chtěl do teď, tak to měl nejspíš nějak takhle?

{var $gid = main-content}

{foreach $products as $id => $product}
    ...
{/foreach}

<div id="{$gid}"></div>

tak že případné ne-přepisování v nové verzi nejspíš takový kód neohrozí.
Každopádně tohle je na Latte super… že těch rozdílů mi přijde minimum.
Oproti třeba twigu, kde ani není foreach… ale je tam for a ještě zápis ala javascript… omg.