Skip to content

Commit 47600d6

Browse files
committed
latte 3.1
1 parent f1acb46 commit 47600d6

File tree

8 files changed

+291
-40
lines changed

8 files changed

+291
-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|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: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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 uses special attributes like `checked`, `disabled`, `selected`, or `hidden`, where the specific value is irrelevant—only their presence matters. They act as simple flags.
12+
13+
Latte 3.1 handles them automatically. You can pass any expression to the attribute. If it is truthy, the attribute is rendered. If it is falsey (e.g. `false`, `null`, `0`, or an empty string), the attribute is completely omitted.
14+
15+
This means you can say goodbye to cumbersome macro conditions or `n:attr`:
16+
17+
```latte
18+
<input type="text" disabled={$isDisabled} readonly={$isReadOnly}>
19+
</form>
20+
```
21+
22+
If `$isReadOnly` is `true` and `$isDisabled` is `false`, it renders:
23+
24+
```latte
25+
<input type="text" readonly>
26+
```
27+
28+
If you need this toggling behavior for standard attributes that don't have this automatic handling (like `data-` or `aria-` attributes), use the [toggle |filters#toggle] filter.
29+
30+
31+
Null Values
32+
===========
33+
34+
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=""`.
35+
36+
In Latte 3.1, a new universal rule applies: **A value of `null` means the attribute does not exist.**
37+
38+
```latte
39+
<div title="{$title}"></div>
40+
```
41+
42+
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.
43+
44+
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 |filters#Nullsafe Filters] `?|`:
45+
46+
```latte
47+
<div title="{$title?|upper}"></div>
48+
```
49+
50+
51+
Classes
52+
=======
53+
54+
You can pass an array to the `class` attribute, and Latte will automatically join the items with spaces.
55+
56+
This is perfect for conditional classes: if the array is associative, the keys are used as class names and the values as conditions. The class is rendered only if the condition is true.
57+
58+
```latte
59+
<div class="header">
60+
<button class={[
61+
btn,
62+
btn-primary,
63+
active => $isActive,
64+
]}>Press me</button>
65+
</div>
66+
```
67+
68+
If `$isActive` is true, it renders:
69+
70+
```latte
71+
<div class="header">
72+
<button class="btn btn-primary active">Press me</button>
73+
</div>
74+
```
75+
76+
This behavior is not limited to `class`. It works for **any HTML attribute** that expects a space-separated list of values, such as `itemprop`, `rel`, `sandbox`, etc.
77+
78+
```latte
79+
<a rel={[nofollow, noopener, external => $isExternal]}>link</a>
80+
```
81+
82+
83+
Styles
84+
======
85+
86+
The `style` attribute also supports arrays. It is especially useful for conditional styles. If an array item contains a key (CSS property) and a value, the property is rendered only if the value is not `null`.
87+
88+
```latte
89+
<div style={[
90+
background => lightblue,
91+
display => $isVisible ? block : null,
92+
font-size => '16px',
93+
]}></div>
94+
```
95+
96+
If `$isVisible` is false, it renders:
97+
98+
```latte
99+
<div style="background: lightblue; font-size: 16px"></div>
100+
```
101+
102+
103+
Data Attributes
104+
===============
105+
106+
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:
107+
108+
```latte
109+
<div data-config={[ theme: dark, version: 2 ]}></div>
110+
```
111+
112+
Outputs:
113+
114+
```latte
115+
<div data-config='{"theme":"dark","version":2}'></div>
116+
```
117+
118+
Also, `true` and `false` are rendered as strings `"true"` and `"false"` (i.e. valid JSON).
119+
120+
121+
Aria Attributes
122+
===============
123+
124+
The WAI-ARIA specification requires text values `"true"` and `"false"` for boolean values. Latte handles this automatically for `aria-` attributes:
125+
126+
```latte
127+
<button aria-expanded={true} aria-checked={false}></button>
128+
```
129+
130+
Outputs:
131+
132+
```latte
133+
<button aria-expanded="true" aria-checked="false"></button>
134+
```
135+
136+
137+
Type Checking
138+
=============
139+
140+
Latte 3.1 checks attribute types to prevent you from printing nonsense.
141+
142+
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.
143+
2. **Boolean attributes (checked...):** Expect a boolean.
144+
3. **Special attributes (class, style, data-, aria-):** Have their own rules described above.
145+
146+
This check helps you discover bugs in your code early.
147+
148+
149+
Migration from Latte 3.0
150+
========================
151+
152+
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.
153+
154+
You can enable **migration warnings** (see [Develop |develop#Migration Warnings]), which will warn you during rendering if the output differs from Latte 3.0.
155+
156+
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:
157+
158+
```latte
159+
<div class="{$var|accept}"></div>
160+
```
161+
162+
If you want to keep the attribute as empty (e.g. `title=""`) instead of dropping it, use the null coalescing operator:
163+
164+
```latte
165+
<div title={$var ?? ''}></div>
166+
```
167+
168+
Or, if you strictly require the old behavior (e.g. `"1"` for `true`), explicitly cast the value to string:
169+
170+
```latte
171+
<div data-foo={(string) $bool}></div>
172+
```
173+
174+
Once all warnings are resolved, disable migration warnings and **remove all** `|accept` filters from your templates, as they are no longer needed.

0 commit comments

Comments
 (0)