Latte 2.9: last but not for least
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:
- Latte 2.6 with optional chaining and custom functions
- Latte 2.7: types everywhere and batch
- Latte 2.8: fortifications inside the template
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
Nice
Beautiful !
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.Wow, nice job! I really appreciate this blog post being in english! 👍 Perfect
#3 meridius Yeah, that makes sense.
#4 Pavel Janda All posts on the blog are in English 🙂
Sign in to submit a comment