-
-
Notifications
You must be signed in to change notification settings - Fork 282
/
extending-latte.texy
291 lines (195 loc) · 14.2 KB
/
extending-latte.texy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
Подовжуючий Latte
*****************
.[perex]
Latte дуже гнучкий і може бути розширений безліччю способів: ви можете додати користувацькі фільтри, функції, теги, завантажувачі тощо. Ми покажемо вам, як це зробити.
У цьому розділі описано різні способи розширення Latte. Якщо ви хочете повторно використовувати свої зміни в різних проєктах або поділитися ними з іншими, вам слід [створити так зване розширення |creating-extension].
Скільки доріг веде до Риму? .[#toc-how-many-roads-lead-to-rome]
===============================================================
Оскільки деякі зі способів розширення Latte можуть змішуватися, давайте спочатку спробуємо пояснити відмінності між ними. Як приклад спробуємо реалізувати генератор *Lorem ipsum*, якому передається кількість слів для генерації.
Основною конструкцією мови Latte є тег. Ми можемо реалізувати генератор, розширивши Latte новим тегом:
```latte
{lipsum 40}
```
Тег працюватиме чудово. Однак генератор у вигляді тега може виявитися недостатньо гнучким, оскільки його не можна використовувати у виразі. До речі, на практиці вам рідко знадобиться генерувати теги; і це хороша новина, тому що теги - це складніший спосіб розширення.
Добре, давайте спробуємо створити фільтр замість тега:
```latte
{=40|lipsum}
```
Знову ж таки, прийнятний варіант. Але фільтр повинен перетворити передане значення на щось інше. Тут ми використовуємо значення `40`, яке вказує на кількість створених слів, як аргумент фільтра, а не як значення, яке ми хочемо перетворити.
Тому давайте спробуємо використати функцію:
```latte
{lipsum(40)}
```
Ось і все! Для цього конкретного прикладу створення функції - ідеальна точка розширення. Ви можете викликати її в будь-якому місці, де приймається вираз, наприклад:
```latte
{var $text = lipsum(40)}
```
Фільтри .[#toc-filters]
=======================
Створіть фільтр, зареєструвавши його ім'я і будь-який елемент PHP, що викликається, наприклад, функцію:
```php
$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // скорочує текст до 10 символів
```
У цьому випадку було б краще, щоб фільтр отримував додатковий параметр:
```php
$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));
```
Ми використовуємо його в шаблоні таким чином:
```latte
<p>{$text|shortify}</p>
<p>{$text|shortify:100}</p>
```
Як бачите, функція отримує як такі аргументи ліву частину фільтра перед трубою `|` as the first argument and the arguments passed to the filter after `:`.
Звичайно, функція, що представляє фільтр, може приймати будь-яку кількість параметрів, також підтримуються змінні параметри.
Якщо фільтр повертає рядок у форматі HTML, ви можете позначити його, щоб Latte не використовував автоматичне (і, відповідно, подвійне) екранування. Це дозволяє уникнути необхідності вказувати `|noescape` у шаблоні.
Найпростіший спосіб - обернути рядок в об'єкт `Latte\Runtime\Html`, інший спосіб - [контекстні фільтри |#Contextual Filters].
```php
$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));
```
.[note]
У цьому випадку фільтр повинен забезпечити коректне екранування даних.
Фільтри, що використовують клас .[#toc-filters-using-the-class]
---------------------------------------------------------------
Другий спосіб визначити фільтр - [використовувати клас |develop#Parameters-as-a-Class]. Ми створюємо метод з атрибутом `TemplateFilter`:
```php
class TemplateParameters
{
public function __construct(
// параметри
) {}
#[Latte\Attributes\TemplateFilter]
public function shortify(string $s, int $len = 10): string
{
return mb_substr($s, 0, $len);
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
```
Завантажувач фільтрів .[#toc-filter-loader]
-------------------------------------------
Замість реєстрації окремих фільтрів ви можете створити так званий завантажувач, який являє собою функцію, що викликається з ім'ям фільтра як аргумент і повертає його викликаний PHP-файл або null.
```php
$latte->addFilterLoader([new Filters, 'load']);
class Filters
{
public function load(string $filter): ?callable
{
if (in_array($filter, get_class_methods($this))) {
return [$this, $filter];
}
return null;
}
public function shortify($s, $len = 10)
{
return mb_substr($s, 0, $len);
}
// ...
}
```
Контекстні фільтри .[#toc-contextual-filters]
---------------------------------------------
Контекстний фільтр - це фільтр, який приймає об'єкт [api:Latte\Runtime\FilterInfo] як перший параметр, за яким слідують інші параметри, як у випадку з класичними фільтрами. Він реєструється так само, Latte сам розпізнає, що фільтр контекстний:
```php
use Latte\Runtime\FilterInfo;
$latte->addFilter('foo', function (FilterInfo $info, string $str): string {
// ...
});
```
Контекстні фільтри можуть визначати і змінювати тип вмісту, який вони отримують у змінній `$info->contentType`. Якщо фільтр викликається класично через змінну (наприклад, `{$var|foo}`), то `$info->contentType` буде містити null.
Фільтр повинен спочатку перевірити, чи підтримується тип вмісту вхідного рядка. Він також може змінити його. Приклад фільтра, який приймає текст (або null) і повертає HTML:
```php
use Latte\Runtime\FilterInfo;
$latte->addFilter('money', function (FilterInfo $info, float $amount): string { })
// спочатку перевіряємо, чи тип вводу текстовий
if (!in_array($info->contentType, [null, ContentType::Text])) {
throw new Exception("Filter |money used in incompatible content type $info->contentType.");
}
// змінюємо тип контенту на HTML
$info->contentType = ContentType::Html;
return "<i>$amount EUR</i>";
});
```
.[note]
У цьому випадку фільтр повинен забезпечити правильне екранування даних.
Усі фільтри, які використовуються поверх [блоків |tags#block] (наприклад, як `{block|foo}...{/block}`), мають бути контекстними.
Функції .[#toc-functions]
=========================
За замовчуванням усі власні функції PHP можуть використовуватися в Latte, якщо тільки це не вимкнено в пісочниці. Але ви також можете визначити свої власні функції. Вони можуть перевизначати власні функції.
Створіть функцію, зареєструвавши її ім'я та будь-яку викликану функцію PHP:
```php
$latte = new Latte\Engine;
$latte->addFunction('random', function (...$args) {
return $args[array_rand($args)];
});
```
Після цього використання буде таким же, як і при виклику PHP-функції:
```latte
{random(apple, orange, lemon)} // prints for example: apple
```
Функції, що використовують клас .[#toc-functions-using-the-class]
-----------------------------------------------------------------
Другий спосіб визначити функцію - [використовувати клас |develop#Parameters-as-a-Class]. Ми створюємо метод з атрибутом `TemplateFunction`:
```php
class TemplateParameters
{
public function __construct(
// параметри
) {}
#[Latte\Attributes\TemplateFunction]
public function random(...$args)
{
return $args[array_rand($args)];
}
}
$params = new TemplateParameters(/* ... */);
$latte->render('template.latte', $params);
```
Завантажувачі .[#toc-loaders]
=============================
Завантажувачі відповідають за завантаження шаблонів із джерела, наприклад, із файлової системи. Вони встановлюються за допомогою методу `setLoader()`:
```php
$latte->setLoader(new MyLoader);
```
Вбудованими завантажувачами є:
FileLoader .[#toc-fileloader]
-----------------------------
Завантажувач за замовчуванням. Завантажує шаблони з файлової системи.
Доступ до файлів можна обмежити, задавши базовий каталог:
```php
$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');
```
StringLoader .[#toc-stringloader]
---------------------------------
Завантажує шаблони з рядків. Цей завантажувач дуже корисний для модульного тестування. Він також може бути використаний для невеликих проектів, де має сенс зберігати всі шаблони в одному PHP-файлі.
```php
$latte->setLoader(new Latte\Loaders\StringLoader([
'main.file' => '{include other.file}',
'other.file' => '{if true} {$var} {/if}',
]));
$latte->render('main.file');
```
Спрощене використання:
```php
$template = '{if true} {$var} {/if}';
$latte->setLoader(new Latte\Loaders\StringLoader);
$latte->render($template);
```
Створення користувацького завантажувача .[#toc-creating-a-custom-loader]
------------------------------------------------------------------------
Loader - це клас, що реалізує інтерфейс [api:Latte\Loader].
Теги .[#toc-tags]
=================
Однією з найцікавіших можливостей шаблонізатора є можливість визначати нові мовні конструкції за допомогою тегів. Це також складніша функціональність, і вам необхідно розуміти, як внутрішньо працює Latte.
У більшості випадків, однак, тег не потрібен:
- якщо він має генерувати певний висновок, використовуйте замість нього [функцію |#Functions]
- якщо потрібно змінити вхідні дані та повернути їх, використовуйте [filter |#Filters]
- якщо потрібно відредагувати ділянку тексту, оберніть її тегом [`{block}` |tags#block] тегом і використовуйте [фільтр |#Contextual-Filters]
- якщо він не повинен був нічого виводити, а тільки викликати функцію, викличте її за допомогою [`{do}` |tags#do]
Якщо ви все ще хочете створити тег, чудово! Все найнеобхідніше можна знайти в розділі [Створення розширення |creating-extension].
Передачі компілятора .[#toc-compiler-passes]
============================================
Паси компілятора - це функції, які змінюють AST або збирають у них інформацію. У Latte, наприклад, пісочниця реалізована таким чином: вона обходить усі вузли AST, знаходить виклики функцій і методів і замінює їх керованими викликами.
Як і у випадку з тегами, це складніша функціональність, і вам потрібно розуміти, як Latte працює під капотом. Усе найнеобхідніше можна знайти в розділі [Створення розширення |creating-extension].