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

David Grudl
Nette Core | 8239
+
+2
-

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
+
+2
-

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)