Skip to content

Commit 78e7677

Browse files
committed
latte 3.1
1 parent f1acb46 commit 78e7677

File tree

8 files changed

+271
-40
lines changed

8 files changed

+271
-40
lines changed

latte/en/@left-menu.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [Template Inheritance]
66
- [Type System]
77
- [Sandbox]
8+
- [HTML attributes]
89

910
- For Designers 🎨
1011
- [Syntax]

latte/en/custom-filters.texy

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -117,30 +117,6 @@ $latte->addExtension(new App\Latte\MyLatteExtension);
117117
This approach keeps your filter logic encapsulated and makes registration straightforward.
118118

119119

120-
Using a Filter Loader
121-
---------------------
122-
123-
Latte allows registering a filter loader via `addFilterLoader()`. This is a single callable that Latte asks for any unknown filter name during compilation. The loader returns the filter's PHP callable or `null`.
124-
125-
```php
126-
$latte = new Latte\Engine;
127-
128-
// Loader might dynamically create/fetch filter callables
129-
$latte->addFilterLoader(function (string $name): ?callable {
130-
if ($name === 'myLazyFilter') {
131-
// Imagine expensive initialization here...
132-
$service = get_some_expensive_service();
133-
return fn($value) => $service->process($value);
134-
}
135-
return null;
136-
});
137-
```
138-
139-
This method was primarily intended for lazy loading filters with very **expensive initialization**. However, modern dependency injection practices usually handle lazy services more effectively.
140-
141-
Filter loaders add complexity and are generally discouraged in favor of direct registration via `addFilter()` or within an Extension using `getFilters()`. Use loaders only if you have a strong, specific reason related to performance bottlenecks in filter initialization that cannot be addressed otherwise.
142-
143-
144120
Filters Using a Class with Attributes .{toc: Filters Using the Class}
145121
---------------------------------------------------------------------
146122

latte/en/develop.texy

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Supported PHP versions (applies to the latest patch Latte versions):
1515

1616
| version | compatible with PHP
1717
|-----------------|-------------------
18-
| Latte 3.0 | PHP 8.0 – 8.2
18+
| Latte 3.1 | PHP 8.2 – 8.5
19+
| Latte 3.0 | PHP 8.0 – 8.5
1920

2021

2122
How to Render a Template
@@ -193,6 +194,27 @@ $latte = new Latte\Engine;
193194
$latte->setStrictTypes();
194195
```
195196

197+
.[note]
198+
Since Latte 3.1, strict types are enabled by default. You can disable them with `$latte->setStrictTypes(false)`.
199+
200+
201+
Migration Warnings .{data-version:3.1}
202+
======================================
203+
204+
Latte 3.1 changes the behavior of some HTML attributes (see [Smart Attributes|html-attributes]). For example, `null` values now drop the attribute instead of printing an empty string. To easily find places where this change affects your templates, you can enable migration warnings:
205+
206+
```php
207+
$latte->setMigrationWarnings();
208+
```
209+
210+
When enabled, Latte checks rendered attributes and triggers a user warning (`E_USER_WARNING`) if the output differs from what Latte 3.0 would have produced. When you encounter a warning, apply one of the solutions:
211+
212+
1. If the new output is correct for your use case (e.g., you prefer the attribute to disappear when `null`), suppress the warning by adding the `|accept` filter
213+
2. If you want the attribute to be rendered as empty (e.g. `title=""`) instead of being dropped when the variable is `null`, provide an empty string as a fallback: `title={$val ?? ''}`
214+
3. If you strictly require the old behavior (e.g., printing `"1"` for `true` instead of `"true"`), explicitly cast the value to a string: `data-foo={(string) $val}`
215+
216+
Once all warnings are resolved, disable migration warnings by removing `setMigrationWarnings()` and **remove all** `|accept` filters from your templates, as they are no longer needed.
217+
196218

197219
Translation in Templates .{toc: TranslatorExtension}
198220
====================================================

latte/en/extending-latte.texy

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,6 @@ $latte->addFilter('truncate', $myTruncate);
4444
// Template usage: {$text|truncate} or {$text|truncate:100}
4545
```
4646

47-
You can also register a **Filter Loader**, a function that dynamically provides filter callables based on the requested name:
48-
49-
```php
50-
$latte->addFilterLoader(fn(string $name) => /* return callable or null */);
51-
```
52-
53-
5447
Use `addFunction()` to register a function usable within template expressions.
5548

5649
```php

latte/en/filters.texy

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ In templates, we can use functions that help modify or reformat data into its fi
5555
| `floor` | [rounds a number down to a given precision |#floor]
5656
| `round` | [rounds a number to a given precision |#round]
5757

58+
.[table-latte-filters]
59+
|## HTML Attributes
60+
| `accept` | [accepts the new behavior of smart attributes |#accept]
61+
| `toggle` | [toggles the presence of an HTML attribute |#toggle]
62+
5863
.[table-latte-filters]
5964
|## Escaping
6065
| `escapeUrl` | [escapes a parameter in a URL |#escapeUrl]
@@ -117,10 +122,31 @@ It is then called in the template like this:
117122
```
118123

119124

125+
Nullsafe Filters .{data-version:3.1}
126+
------------------------------------
127+
128+
Any filter can be made nullsafe by using `?|` instead of `|`. If the value is null, the filter is not executed and null is returned. Subsequent filters in the chain are also skipped.
129+
130+
This is useful in combination with HTML attributes, which are omitted if the value is `null`.
131+
132+
```latte
133+
<div title={$title?|upper}>
134+
{* If $title is null: <div> *}
135+
{* If $title is 'hello': <div title="HELLO"> *}
136+
```
137+
138+
120139
Filters
121140
=======
122141

123142

143+
accept .[filter]{data-version:3.1}
144+
----------------------------------
145+
The filter is used during [migration from Latte 3.0|html-attributes#Migration from Latte 3.0] to acknowledge that you've reviewed the attribute behavior change and accept it. It does not modify the value.
146+
147+
This is a temporary tool. Once the migration is complete and migration warnings are disabled, you should remove this filter from your templates.
148+
149+
124150
batch(int $length, mixed $item): array .[filter]
125151
------------------------------------------------
126152
A filter that simplifies listing linear data in a table format. It returns an array of arrays with the specified number of items. If you provide a second parameter, it will be used to fill in missing items in the last row.
@@ -827,6 +853,21 @@ Extracts a portion of a string. This filter has been replaced by the [#slice] fi
827853
```
828854

829855

856+
toggle .[filter]{data-version:3.1}
857+
----------------------------------
858+
The `toggle` filter controls the presence of an attribute based on a boolean value. If the value is truthy, the attribute is present; if falsy, the attribute is omitted entirely:
859+
860+
```latte
861+
<div uk-grid={$isGrid|toggle}>
862+
{* If $isGrid is truthy: <div uk-grid> *}
863+
{* If $isGrid is falsy: <div> *}
864+
```
865+
866+
This filter is useful for custom attributes or JavaScript library attributes that require presence/absence control similar to HTML boolean attributes.
867+
868+
The filter can only be used within HTML attributes.
869+
870+
830871
translate(...$args) .[filter]
831872
-----------------------------
832873
Translates expressions into other languages. To make the filter available, you need to [set up the translator |develop#TranslatorExtension]. You can also use the [tags for translation |tags#Translation].

latte/en/html-attributes.texy

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
Smart HTML Attributes
2+
*********************
3+
4+
.[perex]
5+
Latte 3.1 comes with a set of improvements that focuses on one of the most common activities in templates – printing HTML attributes. It brings more convenience, flexibility and security.
6+
7+
8+
Boolean Attributes
9+
==================
10+
11+
HTML has a specific category of attributes where the value doesn't matter, but their presence does. These are attributes like `checked`, `disabled`, `selected`, `hidden`, `readonly`, `required`, etc.
12+
13+
Previously, you had to use `n:attr` or conditions. In Latte 3.1, you can pass `true` or `false` directly to the attribute:
14+
15+
```latte
16+
<input hidden={true} readonly={false}>
17+
```
18+
19+
Outputs:
20+
21+
```latte
22+
<input hidden>
23+
```
24+
25+
If the value is `true`, the attribute is rendered. If `false`, it is omitted. This is intuitive and the code is much more readable.
26+
27+
If you are using a JavaScript library that requires the presence/absence of an attribute and you want to achieve this behavior for other attributes as well (e.g. `data-` attributes), use the [toggle |filters#toggle] filter.
28+
29+
30+
Null Values
31+
===========
32+
33+
This is one of the most pleasant changes. Previously, if a variable was `null`, it printed as an empty string `""`. This often led to empty attributes in HTML like `class=""` or `title=""`.
34+
35+
In Latte 3.1, a new universal rule applies: **A value of `null` means the attribute does not exist.**
36+
37+
```latte
38+
<div title="{$title}"></div>
39+
```
40+
41+
If `$title` is `null`, the output is `<div></div>`. If it contains a string, e.g. "Hello", the output is `<div title="Hello"></div>`. Thanks to this, you don't have to wrap attributes in conditions.
42+
43+
If you use filters, keep in mind that they usually convert `null` to a string (e.g. empty string). To prevent this, use the [nullsafe operator |syntax#Nullsafe Filters] `?|`:
44+
45+
```latte
46+
<div title="{$title?|upper}"></div>
47+
```
48+
49+
50+
Classes and Styles
51+
==================
52+
53+
The `n:class` attribute is loved for its ability to compose classes from arrays and conditions. Now the standard `class` attribute gains this superpower as well.
54+
55+
You can pass an array (even an associative one with conditions) to the `class` attribute, exactly as you are used to with `n:class`:
56+
57+
```latte
58+
<div class="header">
59+
<button class={[
60+
'btn',
61+
'btn-primary',
62+
'active' => $isActive,
63+
'disabled' => $isDisabled
64+
]}>Press me</button>
65+
</div>
66+
```
67+
68+
If `$isActive` is true and `$isDisabled` is false, it renders:
69+
70+
```latte
71+
<div class="header">
72+
<button class="btn btn-primary active">Press me</button>
73+
</div>
74+
```
75+
76+
The `style` attribute also gains similar flexibility. Instead of concatenating strings, you can pass an array:
77+
78+
```latte
79+
<div style={[
80+
background: 'lightblue',
81+
display: $isVisible ? 'block' : 'none'
82+
]}></div>
83+
```
84+
85+
86+
Data Attributes
87+
===============
88+
89+
Often we need to pass configuration for JavaScript into HTML. Previously this was done via `json_encode`. Now you can simply pass an array or object to a `data-` attribute and Latte will serialize it to JSON:
90+
91+
```latte
92+
<div data-config={[ theme: 'dark', version: 2 ]}></div>
93+
```
94+
95+
Outputs:
96+
97+
```latte
98+
<div data-config='{"theme":"dark","version":2}'></div>
99+
```
100+
101+
Also, `true` and `false` are rendered as strings `"true"` and `"false"` (i.e. valid JSON).
102+
103+
104+
Aria Attributes
105+
===============
106+
107+
The WAI-ARIA specification requires text values `"true"` and `"false"` for boolean values. Latte handles this automatically for `aria-` attributes:
108+
109+
```latte
110+
<button aria-expanded={true} aria-checked={false}></button>
111+
```
112+
113+
Outputs:
114+
115+
```latte
116+
<button aria-expanded="true" aria-checked="false"></button>
117+
```
118+
119+
120+
Type Checking
121+
=============
122+
123+
Latte 3.1 checks attribute types to prevent you from printing nonsense.
124+
125+
1. **Standard attributes (href, src, id...):** Expect a string or `null`. If they receive an array, object or boolean, Latte throws a warning and the attribute is omitted.
126+
2. **Boolean attributes (checked...):** Expect a boolean.
127+
3. **Special attributes (class, style, data-, aria-):** Have their own rules described above.
128+
129+
This check helps you discover bugs in your code early.
130+
131+
132+
Migration from Latte 3.0
133+
========================
134+
135+
Since the behavior of `null` changes (it used to print `""`, now it doesn't print anything) and `data-` attributes (booleans used to print `1`/`""`, now `"true"`/`"false"`), Latte offers a tool to help with migration.
136+
137+
You can enable **migration warnings** (see [Develop |develop#Migration Warnings]), which will warn you during rendering if the output differs from Latte 3.0.
138+
139+
If the new behavior is correct (e.g. you want the empty attribute to disappear), confirm it using the `|accept` filter to suppress the warning:
140+
141+
```latte
142+
<div class="{$var|accept}"></div>
143+
```
144+
145+
If you want to keep the attribute as empty (e.g. `title=""`) instead of dropping it, use the null coalescing operator:
146+
147+
```latte
148+
<div title={$var ?? ''}></div>
149+
```
150+
151+
Or, if you strictly require the old behavior (e.g. `"1"` for `true`), explicitly cast the value to string:
152+
153+
```latte
154+
<div data-foo={(string) $bool}></div>
155+
```
156+
157+
Once all warnings are resolved, disable migration warnings and **remove all** `|accept` filters from your templates, as they are no longer needed.

latte/en/syntax.texy

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,34 @@ Which outputs, depending on the variable `$url`:
111111

112112
However, n:attributes are not only a shortcut for pair tags, there are some pure n:attributes as well, for example the coder's best friend [n:class|tags#n:class] or the very handy [n:href |application:creating-links#In the Presenter Template].
113113

114+
In addition to the syntax using quotes `<div n:if="$foo">`, you can use alternative syntax with curly braces `<div n:if={$foo}>`. The main advantage is that you can freely use both single and double quotes inside `{...}`:
115+
116+
```latte
117+
<div n:if={str_contains($val, "foo")}> ... </div>
118+
```
119+
120+
121+
Smart HTML Attributes .{data-version:3.1}
122+
=========================================
123+
124+
Latte makes working with standard HTML attributes incredibly easy. It handles boolean attributes like `checked` for you, removes attributes containing `null`, and allows you to compose `class` and `style` values using arrays. It even automatically serializes data for `data-` attributes into JSON.
125+
126+
```latte
127+
{* null removes the attribute *}
128+
<div title={$title}>
129+
130+
{* boolean controls presence of boolean attributes *}
131+
<input type="checkbox" checked={$isChecked}>
132+
133+
{* arrays work in class *}
134+
<div class={['btn', 'btn-primary', active => $isActive]}>
135+
136+
{* arrays are JSON-encoded in data- attributes *}
137+
<div data-config={[theme: dark, version: 2]}>
138+
```
139+
140+
Read more in the separate chapter [Smart HTML Attributes|html-attributes].
141+
114142

115143
Filters
116144
=======
@@ -148,10 +176,17 @@ On a block:
148176
```
149177

150178
Or directly on a value (in combination with the [`{=expr}` |tags#Printing] tag):
179+
151180
```latte
152181
<h1>{=' Hello world '|trim}<h1>
153182
```
154183

184+
If the value can be `null` and you want to avoid applying the filter in that case, use the [nullsafe operator |filters#Nullsafe Filters] `?|`:
185+
186+
```latte
187+
<h1>{$heading?|upper}</h1>
188+
```
189+
155190

156191
Dynamic HTML Tags .{data-version:3.0.9}
157192
=======================================
@@ -204,7 +239,7 @@ Simple strings are those composed purely of letters, digits, underscores, hyphen
204239
Constants
205240
---------
206241

207-
Since quotes can be omitted for simple strings, we recommend writing global constants with a leading slash to distinguish them:
242+
Use the global namespace separator to distinguish global constants from simple strings:
208243

209244
```latte
210245
{if \PROJECT_ID === 1} ... {/if}
@@ -265,8 +300,6 @@ A Window into History
265300

266301
Over its history, Latte introduced several syntactic sugar features that appeared in PHP itself a few years later. For example, in Latte, it was possible to write arrays as `[1, 2, 3]` instead of `array(1, 2, 3)` or use the nullsafe operator `$obj?->foo` long before it was possible in PHP itself. Latte also introduced the array expansion operator `(expand) $arr`, which is equivalent to today's `...$arr` operator from PHP.
267302

268-
The undefined-safe operator `??->`, which is similar to the nullsafe operator `?->` but does not raise an error if the variable does not exist, was created for historical reasons, and today we recommend using the standard PHP operator `?->`.
269-
270303

271304
PHP Limitations in Latte
272305
========================

0 commit comments

Comments
 (0)