Latte 2.9: last but not for least

3 years ago by David Grudl  

The new version brings features that will change the way you work with templates. Coding templates will be much more fun than you ever thought.

Three major versions of the Latte have already been released this year:

But it is the new version 2.9 that brings the most new featues. Let's take a look at them!

{foreach} + {else}

The {foreach} tag can take an optional {else} clause whose text is displayed if the array we iterate over is empty:

<ul>
    {foreach $people as $person}
        <li>{$person->name}</li>
    {else}
        <li><em>Sorry, no users in this list</em></li>
    {/foreach}
</ul>

{skipIf}

It is very similar to {continueIf}, but does not increment the counter. So there are no holes in the numbering when you print $iterator->counter and skip some items. And most importantly, the {else} clause will be rendered when you skip all items. Cool!

<ul>
    {foreach $people as $person}
		{skipIf $person->age < 18}
        <li>{$iterator->counter}. {$person->name}</li>
    {else}
        <li><em>Sorry, no adult users in this list</em></li>
    {/foreach}
</ul>

$iterator: counter0 & parent

Two new properties of the $iterator object has been added. Property $iterator->counter0, sibling of $iterator->counter, but counted from zero.

And property $iterator->parent which returns the iterator surrounding the current one.

{try} & {else} & {rollback}

Awesome new feature that makes it extremely easy to build robust templates. If an exception occurs while rendering the {try} block, the entire block will be skipped and rendering will continue after it:

{try}
	<ul>
		{foreach $twitter->loadTweets() as $tweet}
  			<li>{$tweet->text}</li>
		{/foreach}
	</ul>
{/try}

The optional {else} clause is rendered on exception:

{try}
	<ul>
		{foreach $twitter->loadTweets() as $tweet}
  			<li>{$tweet->text}</li>
		{/foreach}
	</ul>
	{else}
	<p>Sorry, unable to load tweets.</p>
{/try}

The {try} block can also be stopped and skipped manually using {rollback}. You do not have to check all the input data in advance, and you can decide during rendering whether it makes sense to render the object.

{try}
<ul>
    {foreach $people as $person}
 		{skipIf $person->age < 18}
       <li>{$person->name}</li>
    {else}
		{rollback}
    {/foreach}
</ul>
{/try}

It is also possible to define own exception handler for i.e logging:

$latte = new Latte\Engine;
$latte->setExceptionHandler(function (\Throwable $e) {
	...
});

{ifchanged}

Check if a value has changed from the last iteration within a loop (for, foreach, while).

If one or more variables are passed in the tag, it checks to see if any variable has changed. For example, the following example prints a heading with the first letter each time it changes when listing names:

{foreach ($names|sort) as $name}
    {ifchanged $name[0]} <h2>{$name[0]}</h2> {/ifchanged}

	<p>{$name}</p>
{/foreach}

If no argument is specified, it checks its own rendered contents against its previous state and only displays the content if it has changed. So in the previous example, we can omit the argument. We can also use n:attribute:

{foreach ($names|sort) as $name}
    <h2 n:ifchanged>{$name[0]}</h2>

	<p>{$name}</p>
{/foreach}

The {ifchanged} tag can also take an optional {else} clause that will be displayed if the value has not changed.

{embed}

Game-changer in the block inheritance.

Embeding is a mix between both extending and including. Basically it includes a template with the ability to override any block defined inside it, like when extending a template.

{embed 'article.latte', social: ['facebook', 'twitter', 'instagram']}

    {* these blocks defined in article.latte we override right here *}

    {block title}
		<div>Header Content</div>
	{/block}

	{block main}
        <div>Main Content</div>
    {/block}
{/embed}

Template article.latte:

<div class="article-component">
    <header>{block title}Default Header Content{/block}</header>

    <main>{block main}Default Main Content{/block}</main>

    <footer>{block footer}Default Footer Content{/block}</footer>

    {ifset $social}
        <ul class="social">
			<li n:foreach="$social as $item">{$item}</li>
        </ul>
    {/ifset}
</div>

Local blocks

Blocks are shared between multiple templates if they are part of an inheritance hierarchy ({layout}) or if we import them ({import}). But sometimes in such templates we need to create a block (via {block} or {define}) that should not be available in other templates, nor should it rewrite other blocks if their names match.

The solution is to mark such a block as local using word local before name:

{block local tree}
	This is local block
{/block}

{case 1, 2, 3}

Clause {case} can now contain multiple values:

{switch $status}
{case $status::NEW}<b>new item</b>
{case $status::SOLD, $status::UNKNOWN}<i>not available</i>
{/switch}

Because the {switch} in Latte uses strict comparisons and does not need break, it is now the exact equivalent for PHP 8 match statement.

{include block/file} & {ifset block}

The {include} tag can be used to insert blocks or files. Similarly, the {ifset} can be used to test for the existence of a variable or block. Latte recognizes that it is about block if the argument consists of only alphanumeric characters.

It is now possible to distinguish that it is a block resp. file by explicitly specifying the keyword block resp. file, which is useful, for example, if the name is stored in a variable.

{include block $blockName}
{include file $file}
{ifset block $blockName} ... {/ifset}

{include block from file}

You can now insert only a single block from a file using the {include} tag. It also works for local blocks.

{include sidebar from 'template.latte'}

{include file with blocks}

A direct replacement for the deprecated tag {includeblock}, which had an inappropriate name, is this tag:

`{include 'template.latte' with blocks}`

Default values in {define}

Parameters in {define} can have default values. If you do not specify one, it is null as before.

{define sidebar $class, $cols = 2}
	...
{/define}

Filter and function clamp

Historically this is the first default custom function in Latte. Returns value clamped to the inclusive range of min and max.

{=clamp($level, 0, 255)}

{$level|clamp: 0, 255}

Filter |sort

Filter that simply sorts array:

{foreach ($names|sort) as $name}
	...
{/foreach}

Array sorted in reverse order:

{foreach ($names|sort|reverse) as $name}
	...
{/foreach}

PHP 8 influence: named arguments

You can use PHP8-like syntax for named arguments in tags like {include} or {link}:

before
{include 'file.latte' arg1 => 1, arg2 => 2}
now
{include 'file.latte' arg1: 1, arg2: 2}

before
{link default page => 1}
now
{link default page: 1}

Additionally, if you are using PHP 8, you can use named arguments inside all function calls like {=func(a: 10, b: 20)}

Due to future support for named arguments in modifiers, the use of a colon as an argument delimiter is deprecated. Use a comma instead of a colon: {$var|filter: a: 2}{$var|filter: a, 2}

PHP 8 influence: operators ?-> and ??->

Latte came up with a optional chaining notation almost a year ago. PHP 8 now comes up with something really very similar and it is named nullsafe operator:

$var?->prop?->elem[1]?->call()?->item

But the behavior is not exactly the same. Behavior in PHP 8 triggers a warning if a variable, property or array index does not exist (in the given example, if $var, $var->prop or elem[1] does not exist). It just tests if the value is null.

In contrast, the Latte behavior is similar to null coalescing operator ??. Meaning that a non-existent variable, property or array index is not considered as error.

Latte 2.9 unifies the behaviors and the ?-> operator now behaves the same as in PHP to be consistent. But the previous behavior is now implemented by the new undefined-safe operator ??->:

$var??->prop??->elem[1]??->call()

Is there still something missing in Latte?

Comments (RSS)

  1. Nice

    3 years ago
  2. Beautiful !

    3 years ago
  3. Great job!
    Given that “optional chaining” (?->) is now “null-safe chaining”. Wouldn't it be better for the new “undefined-safe operator” (??->) to be named “undefined-safe chaining” to maintain consistency with “null-safe chaining” (?->)? It is chaining, not just coalescing operator after all.

    3 years ago · replied [5] David Grudl
  4. Wow, nice job! I really appreciate this blog post being in english! 👍 Perfect

    3 years ago · replied [5] David Grudl
  5. #3 meridius Yeah, that makes sense.

    #4 Pavel Janda All posts on the blog are in English 🙂

    3 years ago

Sign in to submit a comment