latte icon indicating copy to clipboard operation
latte copied to clipboard

Please add an early exit feature for latte templates

Open BernhardBaumrock opened this issue 3 years ago • 23 comments
trafficstars

  • Explain your intentions

As explained in this forum question I'd need an early exit feature in latte templates: https://forum.nette.org/en/35172-early-exit-in-latte-template-file

This follows the guard clause instead of if/else-nesting, see https://www.youtube.com/watch?v=EumXak7TyQ0 why this can be good for reducing code complexity and making it easier to read and maintain.

  • It's up to you to make a strong case to convince the project's developers of the merits of this feature

I've built a page builder for the ProcessWire CMS that looks like this:

img

These content blocks consist of a controller file and a view file. The controller file is PHP (eg Gallery.php), the view file is LATTE (eg Gallery.latte).

A simple view file could look like this:

<h1>{$block->headline}</h1>
<div n:inner-foreach="$block->images() as $img">
   ...
</div>

Now what I'd like to do is to only render the view file if certain conditions are met. In this example it would be great to only render the gallery view file if the block has at least one uploaded image.

{returnif !$block->images()->count()}
<h1>{$block->headline}</h1>
<div n:inner-foreach="$block->images() as $img">
   ...
</div>

Without this feature I need to wrap my view markup in an if condition:

{if $block->images()->count()}
  <h1>{$block->headline}</h1>
  <div n:inner-foreach="$block->images() as $img">
     ...
  </div>
{/if}

Thx for considering :)

BernhardBaumrock avatar Apr 20 '22 13:04 BernhardBaumrock

Why not simply use plain if? Like this:

{if $block->images()->count()}
  <h1>{$block->headline}</h1>
  <div n:inner-foreach="$block->images() as $img">
     ...
  </div>
{/if}

MartinMystikJonas avatar Apr 20 '22 13:04 MartinMystikJonas

I would probably use skipIf rather than returnIf

dg avatar Apr 20 '22 13:04 dg

@MartinMystikJonas good point, didn't think of that. IMHO it's ugly, so it's partly just a personal preference :) But there are often cases where early exists are a lot cleaner than if/else/and/or...

{returnIf !$block->images()->count()}
{returnIf $block->settings->foo}
{returnIf $block->settings->bar AND $block->title == 'bar'}
<h1>{$block->headline}</h1>
<div n:inner-foreach="$block->images() as $img">
   ...
</div>

Also it does not add unnecessary indentation etc. It's the same as having n:if where you could also wrap the tag in {if}...{/if} but n:if or n:tag-if producing much cleaner code!

BernhardBaumrock avatar Apr 20 '22 14:04 BernhardBaumrock

@dg I would expect skipIf to be a pair tag that bounds what should be skipped. Maybe stopIf would be an option? Or maybe breakIf, even though it's not a loop. returnIf on the other hand best fits to the nature of a template – that it's a function.

dakur avatar Apr 21 '22 07:04 dakur

@dakur that's exactly what skipIf should NOT be. As you describe it {skipIf} would just be the opposite of {if} and that's not what I want. I'm talking of a new concept that skips rendering of the whole latte file if a condition is met. So {skipIf} would not have any closing tag by design.

BernhardBaumrock avatar Apr 21 '22 09:04 BernhardBaumrock

@BernhardBaumrock I know, I understood your proposal. I'm discussing the word to be used and its sense – how it's read and understood in the code.

Word "skip" in my opinion implies that it will be further defined (inside of the tag) what should be skipped, because skipping means that you will continue somewhere further (below), whereas "return"/"break"/"stop" clearly says that the code execution stops and doesn't continue for the rest of the file.

Example – if I see this code:

{returnIf !$block->images()->count()}
<h1>{$block->headline}</h1>
<div n:inner-foreach="$block->images() as $img">
   ...
</div>

I clearly understand that if condition is not met, whole template ends rendering. While if I see

{skipIf !$block->images()->count()}
<h1>{$block->headline}</h1>
<div n:inner-foreach="$block->images() as $img">
   ...
</div>

a question arises: "wait, but what should be skipped? where will it continue?"

At least that's my perception of it.

dakur avatar Apr 21 '22 09:04 dakur

Ah, thx @dakur good point. I think that will also fit better into the principle of latte being very close to plain PHP and not a different language. +1 for returnIf :)

BernhardBaumrock avatar Apr 21 '22 11:04 BernhardBaumrock

For me it was about reusing existing tag in a new context https://latte.nette.org/en/tags#toc-continueif-skipif-breakif

dg avatar Apr 21 '22 11:04 dg

In that context, skipIf seems like a solution for a particular problem – being able to continueIf while preserving the counter. But still, the loop gives a sense of the context – what is being skipped. When the context is whole template file, I think it's not so obvious. I can see more similarity to breakIf than to skipIf.

But it's just wording, the point is the functionality of course, words can be changed any later. :-)

dakur avatar Apr 21 '22 12:04 dakur

@dg What about somehow extend the ifContent macro? To be able hide parents/siblings?

milo avatar Apr 21 '22 13:04 milo

I see it exactly the opposite :-) The term return doesn't mean anything to me in the context of templates. What to return? On the contrary, it is common to use skip to … ehm … skipping https://phpunit.readthedocs.io/en/9.5/incomplete-and-skipped-tests.html#skipping-tests https://maven.apache.org/plugins-archives/maven-surefire-plugin-2.12.4/examples/skipping-test.html https://github.com/cypress-io/cypress-skip-test etc

@milo it is not about content, but about your conditions

dg avatar Apr 21 '22 13:04 dg

In tests, you skip whole test, you don't say "skip" from the middle of the test. But it's true that Bernhard posted a use-case with skip at the beginning, not in the middle, so I give up. 🙂

dakur avatar Apr 21 '22 13:04 dakur

I understood it from the beginning as early exit, i.e. ending in header.

Maybe we could also use endIf.

dg avatar Apr 21 '22 14:04 dg

For me none of skipIf, breakIf or endIf feels right. skipIf and breakIf do belong to loops. And endIf belongs to a preceeding {if}.

If you don't like returnIf, what about exitIf?

{exitIf !$block->images()->count()}
{exitIf $block->settings->foo}
{exitIf $block->settings->bar AND $block->title == 'bar'}
<h1>{$block->headline}</h1>
<div n:inner-foreach="$block->images() as $img">
   ...
</div>

BernhardBaumrock avatar Apr 21 '22 14:04 BernhardBaumrock

I'm gonna try to do a poll on this. https://twitter.com/nettefw/status/1517165228898848769

dg avatar Apr 21 '22 15:04 dg

exitIf - similar to exit() skipIf - already used in different context endIf - can be confused with {/if} returnIf - IMHO best but not exactly right either

What about terminateIf?

MartinMystikJonas avatar Apr 21 '22 16:04 MartinMystikJonas

Few quick thoughts:

abortIf breakIf cancelIf discontinueIf haltIf endIf

Dne čt 21. dub 2022 18:19 uživatel Martin Mystik Jonáš < @.***> napsal:

exitIf - similar to exit() skipIf - already used in different context endIf - can be confused with {/if} returnIf - IMHO best but not exactly right either

What about terminateIf?

— Reply to this email directly, view it on GitHub https://github.com/nette/latte/issues/287#issuecomment-1105434831, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABROI46VIQKIK5VDFY77F3VGF5YTANCNFSM5T4DGF7Q . You are receiving this because you are subscribed to this thread.Message ID: @.***>

pavelmlejnek avatar Apr 21 '22 17:04 pavelmlejnek

finishIf

vojtech-dobes avatar Apr 21 '22 18:04 vojtech-dobes

The goal was to reach a consensus :-)

dg avatar Apr 21 '22 18:04 dg

What about the opposite meaning?

{renderIf $block->images()->count()}

<h1>{$block->headline}</h1>
<div n:inner-foreach="$block->images() as $img">
   ...
</div>

milo avatar Apr 22 '22 08:04 milo

Just to throw in more wrenches, I'll suggest stopIf. 😁

But seriously, I don't really care about the name. I just wanted to chime in suggesting to avoid tags containing return or exit since both are loaded with a different meaning in PHP.

loilo avatar Jun 13 '22 20:06 loilo

The quitIf and leaveIf hasn't come up yet :)

dg avatar Jun 13 '22 21:06 dg

Ok I didn't know back in April, but now I know that my suggested priciple does actually have a name and is called "guard clause". A guard clause is here to reduce complexity and make code more readable, which is exactly why I proposed the feature. Read here: https://deviq.com/design-patterns/guard-clause or see here: https://www.youtube.com/watch?v=EumXak7TyQ0

BernhardBaumrock avatar Aug 28 '22 11:08 BernhardBaumrock

Well i've always used just php return since 2.4 but now with 2.* it force me to use "return []"

badpenguin avatar Sep 24 '22 16:09 badpenguin

any news on exitIf @dg ? With some time passed by I think exit is really the best wording :)

BernhardBaumrock avatar Jan 09 '23 15:01 BernhardBaumrock

@badpenguin what do you mean? I don't understand. On my end there seems to be no easy way out of a rendered latte file once rendering has started...

BernhardBaumrock avatar Jan 09 '23 15:01 BernhardBaumrock

I just do <?php return; ?>

badpenguin avatar Jan 09 '23 16:01 badpenguin

@badpenguin we are talking about LATTE files, not PHP files ;) That's exactly what I'm requesting. Make a feature that is extremely easy to do in PHP also available to LATTE.

BernhardBaumrock avatar Jan 09 '23 16:01 BernhardBaumrock

@dg related to this discussion there's also another request/question:

In the PW forum someone came up with a hacky "includeIF" solution:

{include $foo == 'bar' ? "test.latte" : "blank.latte"}

The problem is that you need to define a second template otherwise latte throws an error.

It would be nice to have something like this:

{include $foo == 'bar' ? 'somefile.latte'}

BernhardBaumrock avatar Jan 10 '23 11:01 BernhardBaumrock

@BernhardBaumrock I find this more understandable:

{if $foo == 'bar'}{include 'somefile.latte'}{/if}

dg avatar Jan 12 '23 02:01 dg