/
creating-extension.texy
597 lines (444 loc) · 35.6 KB
/
creating-extension.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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
Δημιουργία μιας επέκτασης
*************************
.[perex]
Μια επέκταση είναι μια επαναχρησιμοποιήσιμη κλάση που μπορεί να ορίσει προσαρμοσμένες ετικέτες, φίλτρα, λειτουργίες, παρόχους κ.λπ.
Δημιουργούμε επεκτάσεις όταν θέλουμε να επαναχρησιμοποιήσουμε τις προσαρμογές μας στο Latte σε διαφορετικά έργα ή να τις μοιραστούμε με άλλους.
Είναι επίσης χρήσιμο να δημιουργήσετε μια επέκταση για κάθε έργο ιστού που θα περιέχει όλες τις συγκεκριμένες ετικέτες και φίλτρα που θέλετε να χρησιμοποιήσετε στα πρότυπα του έργου.
Κλάση επέκτασης .[#toc-extension-class]
=======================================
Η Extension είναι μια κλάση που κληρονομεί από την [api:Latte\Extension]. Καταχωρείται στο Latte χρησιμοποιώντας την `addExtension()` (ή μέσω του [αρχείου ρυθμίσεων |application:configuration#Latte]):
```php
$latte = new Latte\Engine;
$latte->addExtension(new MyLatteExtension);
```
Εάν καταχωρήσετε πολλαπλές επεκτάσεις και ορίζουν ετικέτες, φίλτρα ή λειτουργίες με το ίδιο όνομα, κερδίζει η επέκταση που προστέθηκε τελευταία. Αυτό σημαίνει επίσης ότι οι επεκτάσεις σας μπορούν να παρακάμπτουν τις εγγενείς ετικέτες/φίλτρα/λειτουργίες.
Κάθε φορά που κάνετε μια αλλαγή σε μια κλάση και η αυτόματη ανανέωση δεν είναι απενεργοποιημένη, το Latte θα μεταγλωττίζει αυτόματα ξανά τα πρότυπα σας.
Μια κλάση μπορεί να υλοποιεί οποιαδήποτε από τις ακόλουθες μεθόδους:
```php
abstract class Extension
{
/**
* Initializes before template is compiler.
*/
public function beforeCompile(Engine $engine): void;
/**
* Returns a list of parsers for Latte tags.
* @return array<string, callable>
*/
public function getTags(): array;
/**
* Returns a list of compiler passes.
* @return array<string, callable>
*/
public function getPasses(): array;
/**
* Returns a list of |filters.
* @return array<string, callable>
*/
public function getFilters(): array;
/**
* Returns a list of functions used in templates.
* @return array<string, callable>
*/
public function getFunctions(): array;
/**
* Returns a list of providers.
* @return array<mixed>
*/
public function getProviders(): array;
/**
* Returns a value to distinguish multiple versions of the template.
*/
public function getCacheKey(Engine $engine): mixed;
/**
* Initializes before template is rendered.
*/
public function beforeRender(Template $template): void;
}
```
Για μια ιδέα του πώς μοιάζει η επέκταση, ρίξτε μια ματιά στην ενσωματωμένη "CoreExtension:https://github.com/nette/latte/blob/master/src/Latte/Essential/CoreExtension.php".
beforeCompile(Latte\Engine $engine): void .[method]
---------------------------------------------------
Καλείται πριν από τη μεταγλώττιση του προτύπου. Η μέθοδος μπορεί να χρησιμοποιηθεί για αρχικοποιήσεις που σχετίζονται με τη μεταγλώττιση, για παράδειγμα.
getTags(): array .[method]
--------------------------
Καλείται κατά τη μεταγλώττιση του προτύπου. Επιστρέφει έναν συσχετιστικό πίνακα *όνομα ετικέτας => callable*, οι οποίες είναι [συναρτήσεις ανάλυσης ετικέτας |#Tag Parsing Function].
```php
public function getTags(): array
{
return [
'foo' => [FooNode::class, 'create'],
'bar' => [BarNode::class, 'create'],
'n:baz' => [NBazNode::class, 'create'],
// ...
];
}
```
Η ετικέτα `n:baz` αντιπροσωπεύει ένα καθαρό n:attribute, δηλαδή είναι μια ετικέτα που μπορεί να γραφτεί μόνο ως attribute.
Στην περίπτωση των ετικετών `foo` και `bar`, το Latte θα αναγνωρίσει αυτόματα αν πρόκειται για ζεύγη, και αν ναι, μπορούν να γραφτούν αυτόματα με τη χρήση n:attributes, συμπεριλαμβανομένων των παραλλαγών με τα προθέματα `n:inner-foo` και `n:tag-foo`.
Η σειρά εκτέλεσης αυτών των n:attributes καθορίζεται από τη σειρά τους στον πίνακα που επιστρέφει η `getTags()`. Έτσι, το `n:foo` εκτελείται πάντα πριν από το `n:bar`, ακόμη και αν τα χαρακτηριστικά παρατίθενται με αντίστροφη σειρά στην ετικέτα HTML ως `<div n:bar="..." n:foo="...">`.
Εάν πρέπει να καθορίσετε τη σειρά των n:attributes σε πολλαπλές επεκτάσεις, χρησιμοποιήστε τη βοηθητική μέθοδο `order()`, όπου η παράμετρος `before` xor `after` καθορίζει ποιες ετικέτες διατάσσονται πριν ή μετά την ετικέτα.
```php
public function getTags(): array
{
return [
'foo' => self::order([FooNode::class, 'create'], before: 'bar')]
'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])]
];
}
```
getPasses(): array .[method]
----------------------------
Καλείται κατά τη μεταγλώττιση του προτύπου. Επιστρέφει έναν συσχετιστικό πίνακα *name pass => callable*, οι οποίες είναι συναρτήσεις που αντιπροσωπεύουν τα λεγόμενα [περάσματα του μεταγλωττιστή |#compiler passes] που διασχίζουν και τροποποιούν το AST.
Και πάλι μπορεί να χρησιμοποιηθεί η βοηθητική μέθοδος `order()`. Η τιμή των παραμέτρων `before` ή `after` μπορεί να είναι `*` με την έννοια πριν/μετά από όλα.
```php
public function getPasses(): array
{
return [
'optimize' => [Passes::class, 'optimizePass'],
'sandbox' => self::order([$this, 'sandboxPass'], before: '*'),
// ...
];
}
```
beforeRender(Latte\Engine $engine): void .[method]
--------------------------------------------------
Καλείται πριν από κάθε απόδοση προτύπου. Η μέθοδος μπορεί να χρησιμοποιηθεί, για παράδειγμα, για την αρχικοποίηση μεταβλητών που χρησιμοποιούνται κατά την απόδοση.
getFilters(): array .[method]
-----------------------------
Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει [τα φίλτρα |extending-latte#filters] ως συσχετιστικό πίνακα *όνομα φίλτρου => callable*.
```php
public function getFilters(): array
{
return [
'batch' => [$this, 'batchFilter'],
'trim' => [$this, 'trimFilter'],
// ...
];
}
```
getFunctions(): array .[method]
-------------------------------
Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει τις [συναρτήσεις |extending-latte#functions] ως συσχετιστικό πίνακα *όνομα συνάρτησης => callable*.
```php
public function getFunctions(): array
{
return [
'clamp' => [$this, 'clampFunction'],
'divisibleBy' => [$this, 'divisibleByFunction'],
// ...
];
}
```
getProviders(): array .[method]
-------------------------------
Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει έναν πίνακα παρόχων, οι οποίοι είναι συνήθως αντικείμενα που χρησιμοποιούν ετικέτες κατά την εκτέλεση. Η πρόσβαση σε αυτούς γίνεται μέσω του `$this->global->...`.
```php
public function getProviders(): array
{
return [
'myFoo' => $this->foo,
'myBar' => $this->bar,
// ...
];
}
```
getCacheKey(Latte\Engine $engine): mixed .[method]
--------------------------------------------------
Καλείται πριν από την απόδοση του προτύπου. Η τιμή επιστροφής γίνεται μέρος του κλειδιού του οποίου ο κατακερματισμός περιέχεται στο όνομα του μεταγλωττισμένου αρχείου προτύπου. Έτσι, για διαφορετικές τιμές επιστροφής, η Latte θα δημιουργήσει διαφορετικά αρχεία κρυφής μνήμης.
Πώς λειτουργεί η Latte; .[#toc-how-does-latte-work]
===================================================
Για να καταλάβετε πώς να ορίσετε προσαρμοσμένες ετικέτες ή περάσματα μεταγλωττιστή, είναι απαραίτητο να καταλάβετε πώς λειτουργεί το Latte κάτω από την κουκούλα.
Η μεταγλώττιση προτύπων στο Latte απλουστευτικά λειτουργεί ως εξής:
- Πρώτον, ο **lexer** μετατρέπει τον πηγαίο κώδικα του προτύπου σε μικρά κομμάτια (tokens) για ευκολότερη επεξεργασία.
- Στη συνέχεια, ο **διαχωριστής** μετατρέπει τη ροή των tokens σε ένα ουσιαστικό δέντρο κόμβων (το Αφηρημένο Συντακτικό Δέντρο, AST).
- Τέλος, ο μεταγλωττιστής **δημιουργεί** μια κλάση PHP από το AST που αποδίδει το πρότυπο και το αποθηκεύει.
Στην πραγματικότητα, η μεταγλώττιση είναι λίγο πιο περίπλοκη. Το Latte **έχει δύο** λεξικογράφους και αναλυτές: έναν για το πρότυπο HTML και έναν για τον κώδικα που μοιάζει με PHP μέσα στις ετικέτες. Επίσης, η ανάλυση δεν εκτελείται μετά τη tokenization, αλλά ο lexer και ο parser τρέχουν παράλληλα σε δύο "νήματα" και συντονίζονται. Είναι επιστήμη πυραύλων :-)
Επιπλέον, όλες οι ετικέτες έχουν τις δικές τους ρουτίνες ανάλυσης. Όταν ο αναλυτής συναντά μια ετικέτα, καλεί τη συνάρτηση ανάλυσης της (επιστρέφει την [Extension::getTags() |#getTags]).
Η δουλειά τους είναι να αναλύσουν τα ορίσματα της ετικέτας και, στην περίπτωση των ζευγαρωμένων ετικετών, το εσωτερικό περιεχόμενο. Επιστρέφει έναν *κόμβο* που γίνεται μέρος του AST. Ανατρέξτε στη [λειτουργία ανάλυσης ετικετών |#Tag parsing function] για λεπτομέρειες.
Όταν ο αναλυτής ολοκληρώσει τη δουλειά του, έχουμε ένα πλήρες AST που αναπαριστά το πρότυπο. Ο κόμβος ρίζα είναι το `Latte\Compiler\Nodes\TemplateNode`. Στη συνέχεια, οι επιμέρους κόμβοι μέσα στο δέντρο αντιπροσωπεύουν όχι μόνο τις ετικέτες, αλλά και τα στοιχεία HTML, τα χαρακτηριστικά τους, τυχόν εκφράσεις που χρησιμοποιούνται μέσα στις ετικέτες κ.λπ.
Στη συνέχεια, μπαίνουν στο παιχνίδι τα λεγόμενα [περάσματα του μεταγλωττιστή |#Compiler passes], τα οποία είναι συναρτήσεις (που επιστρέφονται από την [Extension::getPasses() |#getPasses]) που τροποποιούν το AST.
Όλη η διαδικασία, από τη φόρτωση του περιεχομένου του προτύπου, μέσω της ανάλυσης, μέχρι τη δημιουργία του αρχείου που προκύπτει, μπορεί να ακολουθηθεί με αυτόν τον κώδικα, με τον οποίο μπορείτε να πειραματιστείτε και να απορρίψετε τα ενδιάμεσα αποτελέσματα:
```php
$latte = new Latte\Engine;
$source = $latte->getLoader()->getContent($file);
$ast = $latte->parse($source);
$latte->applyPasses($ast);
$code = $latte->generate($ast, $file);
```
Παράδειγμα AST .[#toc-example-of-ast]
-------------------------------------
Για να έχουμε μια καλύτερη ιδέα του AST, προσθέτουμε ένα δείγμα. Αυτό είναι το πρότυπο πηγής:
```latte
{foreach $category->getItems() as $item}
<li>{$item->name|upper}</li>
{else}
no items found
{/foreach}
```
Και αυτή είναι η αναπαράστασή του με τη μορφή AST:
/--pre
Latte\Compiler\Nodes\<b>TemplateNode</b>(
Latte\Compiler\Nodes\<b>FragmentNode</b>(
- Latte\Essential\Nodes\<b>ForeachNode</b>(
expression: Latte\Compiler\Nodes\Php\Expression\<b>MethodCallNode</b>(
object: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$category')
name: Latte\Compiler\Nodes\Php\<b>IdentifierNode</b>('getItems')
)
value: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$item')
content: Latte\Compiler\Nodes\<b>FragmentNode</b>(
- Latte\Compiler\Nodes\<b>TextNode</b>(' ')
- Latte\Compiler\Nodes\<b>Html\ElementNode</b>('li')(
content: Latte\Essential\Nodes\<b>PrintNode</b>(
expression: Latte\Compiler\Nodes\Php\Expression\<b>PropertyFetchNode</b>(
object: Latte\Compiler\Nodes\Php\Expression\<b>VariableNode</b>('$item')
name: Latte\Compiler\Nodes\Php\<b>IdentifierNode</b>('name')
)
modifier: Latte\Compiler\Nodes\Php\<b>ModifierNode</b>(
filters:
- Latte\Compiler\Nodes\Php\<b>FilterNode</b>('upper')
)
)
)
)
else: Latte\Compiler\Nodes\<b>FragmentNode</b>(
- Latte\Compiler\Nodes\<b>TextNode</b>('no items found')
)
)
)
)
\--
Προσαρμοσμένες ετικέτες .[#toc-custom-tags]
===========================================
Τρία βήματα απαιτούνται για τον ορισμό μιας νέας ετικέτας:
- ορισμός [συνάρτησης ανάλυσης ετικέτας |#tag parsing function] (υπεύθυνη για την ανάλυση της ετικέτας σε κόμβο)
- δημιουργία μιας κλάσης κόμβου (υπεύθυνη για [τη δημιουργία κώδικα PHP |#generating PHP code] και τη [διάσχιση AST |#AST traversing])
- καταχώριση της ετικέτας με τη χρήση της [Extension::getTags() |#getTags]
Λειτουργία ανάλυσης ετικέτας .[#toc-tag-parsing-function]
---------------------------------------------------------
Η ανάλυση των ετικετών γίνεται από τη συνάρτηση ανάλυσης (αυτή που επιστρέφεται από την [Extension::getTags() |#getTags]). Η δουλειά της είναι να αναλύει και να ελέγχει τυχόν ορίσματα μέσα στην ετικέτα (χρησιμοποιεί τον TagParser για να το κάνει αυτό).
Επιπλέον, αν η ετικέτα είναι ένα ζεύγος, θα ζητήσει από τον TemplateParser να αναλύσει και να επιστρέψει το εσωτερικό περιεχόμενο.
Η συνάρτηση δημιουργεί και επιστρέφει έναν κόμβο, ο οποίος είναι συνήθως παιδί του `Latte\Compiler\Nodes\StatementNode`, και αυτός γίνεται μέρος του AST.
Δημιουργούμε μια κλάση για κάθε κόμβο, κάτι που θα κάνουμε τώρα, και τοποθετούμε κομψά τη συνάρτηση ανάλυσης σε αυτήν ως στατικό εργοστάσιο. Ως παράδειγμα, ας δοκιμάσουμε να δημιουργήσουμε τη γνωστή ετικέτα `{foreach}`:
```php
use Latte\Compiler\Nodes\StatementNode;
class ForeachNode extends StatementNode
{
// μια συνάρτηση ανάλυσης που απλά δημιουργεί έναν κόμβο προς το παρόν
public static function create(Latte\Compiler\Tag $tag): self
{
$node = $tag->node = new self;
return $node;
}
public function print(Latte\Compiler\PrintContext $context): string
{
// ο κώδικας θα προστεθεί αργότερα
}
public function &getIterator(): \Generator
{
// ο κώδικας θα προστεθεί αργότερα
}
}
```
Στη συνάρτηση ανάλυσης `create()` περνάει ένα αντικείμενο [api:Latte\Compiler\Tag], το οποίο μεταφέρει βασικές πληροφορίες για την ετικέτα (αν είναι κλασική ετικέτα ή n:attribute, σε ποια γραμμή βρίσκεται, κ.λπ.) και κυρίως προσπελαύνει το [api:Latte\Compiler\TagParser] στο `$tag->parser`.
Εάν η ετικέτα πρέπει να έχει ορίσματα, ελέγξτε την ύπαρξή τους καλώντας το `$tag->expectArguments()`. Οι μέθοδοι του αντικειμένου `$tag->parser` είναι διαθέσιμες για την ανάλυσή τους:
- `parseExpression(): ExpressionNode` για μια έκφραση τύπου PHP (π.χ. `10 + 3`)
- `parseUnquotedStringOrExpression(): ExpressionNode` για μια έκφραση ή μια συμβολοσειρά χωρίς εισαγωγικά
- `parseArguments(): ArrayNode` περιεχόμενο του πίνακα (π.χ. `10, true, foo => bar`)
- `parseModifier(): ModifierNode` για έναν τροποποιητή (π.χ. `|upper|truncate:10`)
- `parseType(): expressionNode` για typehint (π.χ. `int|string` ή `Foo\Bar[]`)
και ένα χαμηλού επιπέδου [api:Latte\Compiler\TokenStream] που λειτουργεί απευθείας με tokens:
- `$tag->parser->stream->consume(...): Token`
- `$tag->parser->stream->tryConsume(...): ?Token`
Το Latte επεκτείνει τη σύνταξη της PHP με μικρούς τρόπους, για παράδειγμα προσθέτοντας τροποποιητές, συντομευμένους τριμερείς τελεστές ή επιτρέποντας την εγγραφή απλών αλφαριθμητικών συμβολοσειρών χωρίς εισαγωγικά. Αυτός είναι ο λόγος για τον οποίο χρησιμοποιούμε τον όρο *PHP-like* αντί για PHP. Έτσι, η μέθοδος `parseExpression()` αναλύει το `foo` ως `'foo'`, για παράδειγμα.
Επιπλέον, η *unquoted-string* είναι μια ειδική περίπτωση συμβολοσειράς που επίσης δεν χρειάζεται να είναι σε εισαγωγικά, αλλά ταυτόχρονα δεν χρειάζεται να είναι αλφαριθμητική. Για παράδειγμα, είναι η διαδρομή προς ένα αρχείο στην ετικέτα `{include ../file.latte}`. Η μέθοδος `parseUnquotedStringOrExpression()` χρησιμοποιείται για την ανάλυσή του.
.[note]
Η μελέτη των κλάσεων κόμβων που αποτελούν μέρος του Latte είναι ο καλύτερος τρόπος για να μάθετε όλες τις μικρές λεπτομέρειες της διαδικασίας ανάλυσης.
Ας επιστρέψουμε στην ετικέτα `{foreach}`. Σε αυτήν, περιμένουμε ορίσματα της μορφής `expression + 'as' + second expression`, τα οποία αναλύουμε ως εξής:
```php
use Latte\Compiler\Nodes\StatementNode;
use Latte\Compiler\Nodes\Php\ExpressionNode;
use Latte\Compiler\Nodes\AreaNode;
class ForeachNode extends StatementNode
{
public ExpressionNode $expression;
public ExpressionNode $value;
public static function create(Latte\Compiler\Tag $tag): self
{
$tag->expectArguments();
$node = $tag->node = new self;
$node->expression = $tag->parser->parseExpression();
$tag->parser->stream->consume('as');
$node->value = $parser->parseExpression();
return $node;
}
}
```
Οι εκφράσεις που έχουμε γράψει στις μεταβλητές `$expression` και `$value` αντιπροσωπεύουν υποκόμβους.
.[tip]
Ορίστε τις μεταβλητές με υποκόμβους ως **δημόσιες**, ώστε να μπορούν να τροποποιηθούν σε [περαιτέρω βήματα επεξεργασίας |#Compiler Passes], αν χρειαστεί. Είναι επίσης απαραίτητο να **διαθέσουμε** τις μεταβλητές αυτές για [διάσχιση |#AST Traversing].
Για ζευγαρωτές ετικέτες, όπως η δική μας, η μέθοδος πρέπει επίσης να επιτρέπει στον TemplateParser να αναλύει τα εσωτερικά περιεχόμενα της ετικέτας. Αυτό αντιμετωπίζεται από το `yield`, το οποίο επιστρέφει ένα ζεύγος ''[εσωτερικό περιεχόμενο, τέλος ετικέτας]''. Αποθηκεύουμε το εσωτερικό περιεχόμενο στη μεταβλητή `$node->content`.
```php
public AreaNode $content;
public static function create(Latte\Compiler\Tag $tag): \Generator
{
// ...
[$node->content, $endTag] = yield;
return $node;
}
```
Η λέξη-κλειδί `yield` προκαλεί τον τερματισμό της μεθόδου `create()`, επιστρέφοντας τον έλεγχο πίσω στον TemplateParser, ο οποίος συνεχίζει την ανάλυση του περιεχομένου μέχρι να συναντήσει την ετικέτα end. Στη συνέχεια περνάει τον έλεγχο πίσω στο `create()`, το οποίο συνεχίζει από εκεί που σταμάτησε. Η χρήση της μεθόδου `yield`, επιστρέφει αυτόματα τη μέθοδο `Generator`.
Μπορείτε επίσης να περάσετε έναν πίνακα ονομάτων ετικετών στο `yield` για τις οποίες θέλετε να σταματήσετε την ανάλυση, αν εμφανιστούν πριν από την ετικέτα τέλους. Αυτό μας βοηθάει να υλοποιήσουμε την `{foreach}...{else}...{/foreach}` construct. Εάν εμφανιστεί το `{else}`, αναλύουμε το περιεχόμενο μετά από αυτό στο `$node->elseContent`:
```php
public AreaNode $content;
public ?AreaNode $elseContent = null;
public static function create(Latte\Compiler\Tag $tag): \Generator
{
// ...
[$node->content, $nextTag] = yield ['else'];
if ($nextTag?->name === 'else') {
[$node->elseContent] = yield;
}
return $node;
}
```
Με την επιστροφή του κόμβου ολοκληρώνεται η ανάλυση της ετικέτας.
Δημιουργία κώδικα PHP .[#toc-generating-php-code]
-------------------------------------------------
Κάθε κόμβος πρέπει να υλοποιεί τη μέθοδο `print()`. Επιστρέφει κώδικα PHP που αποδίδει το συγκεκριμένο τμήμα του προτύπου (κώδικας εκτέλεσης). Παραδίδεται ένα αντικείμενο [api:Latte\Compiler\PrintContext] ως παράμετρος, το οποίο διαθέτει μια χρήσιμη μέθοδο `format()` που απλοποιεί τη συναρμολόγηση του κώδικα που προκύπτει.
Η μέθοδος `format(string $mask, ...$args)` δέχεται τα ακόλουθα placeholders στη μάσκα:
- `%node` εκτυπώνει τον κόμβο
- `%dump` εξάγει την τιμή στην PHP
- `%raw` εισάγει το κείμενο απευθείας χωρίς μετασχηματισμό
- `%args` εκτυπώνει ArrayNode ως ορίσματα στην κλήση της συνάρτησης
- `%line` εκτυπώνει ένα σχόλιο με αριθμό γραμμής
- `%escape(...)` διαφυγή του περιεχομένου
- `%modify(...)` εφαρμόζει τροποποιητή
- `%modifyContent(...)` εφαρμόζει τροποποιητή σε μπλοκ
Η συνάρτηση `print()` θα μπορούσε να μοιάζει ως εξής (παραλείπουμε τον κλάδο `else` για λόγους απλότητας):
```php
public function print(Latte\Compiler\PrintContext $context): string
{
return $context->format(
<<<'XX'
foreach (%node as %node) %line {
%node
}
XX,
$this->expression,
$this->value,
$this->position,
$this->content,
);
}
```
Η μεταβλητή `$this->position` έχει ήδη οριστεί από την κλάση [api:Latte\Compiler\Node] και ορίζεται από τον αναλυτή. Περιέχει ένα αντικείμενο [api:Latte\Compiler\Position] με τη θέση της ετικέτας στον πηγαίο κώδικα με τη μορφή αριθμού γραμμής και στήλης.
Ο κώδικας εκτέλεσης μπορεί να χρησιμοποιεί βοηθητικές μεταβλητές. Για να αποφευχθεί η σύγκρουση με τις μεταβλητές που χρησιμοποιούνται από το ίδιο το πρότυπο, είναι σύμβαση να τους προτάσσεται με χαρακτήρες `$ʟ__`.
Μπορεί επίσης να χρησιμοποιεί αυθαίρετες τιμές κατά την εκτέλεση, οι οποίες περνούν στο πρότυπο με τη μορφή παρόχων χρησιμοποιώντας τη μέθοδο [Extension::getProviders() |#getProviders]. Η πρόσβαση σε αυτές γίνεται με τη χρήση του `$this->global->...`.
Διασχίζοντας το AST .[#toc-ast-traversing]
------------------------------------------
Για να διασχίσουμε το δέντρο AST σε βάθος, είναι απαραίτητο να υλοποιήσουμε τη μέθοδο `getIterator()`. Αυτό θα παρέχει πρόσβαση σε υποκόμβους:
```php
public function &getIterator(): \Generator
{
yield $this->expression;
yield $this->value;
yield $this->content;
if ($this->elseContent) {
yield $this->elseContent;
}
}
```
Σημειώστε ότι η `getIterator()` επιστρέφει μια αναφορά. Αυτό είναι που επιτρέπει στους επισκέπτες των κόμβων να αντικαταστήσουν μεμονωμένους κόμβους με άλλους κόμβους.
.[warning]
Εάν ένας κόμβος έχει υποκόμβους, είναι απαραίτητο να υλοποιήσετε αυτή τη μέθοδο και να καταστήσετε όλους τους υποκόμβους διαθέσιμους. Διαφορετικά, θα μπορούσε να δημιουργηθεί ένα κενό ασφαλείας. Για παράδειγμα, η λειτουργία sandbox δεν θα μπορούσε να ελέγξει τους υποκόμβους και να διασφαλίσει ότι δεν θα καλούνται σε αυτούς μη επιτρεπόμενες κατασκευές.
Δεδομένου ότι η λέξη-κλειδί `yield` πρέπει να υπάρχει στο σώμα της μεθόδου, ακόμη και αν δεν έχει κόμβους-παιδιά, γράψτε την ως εξής:
```php
public function &getIterator(): \Generator
{
if (false) {
yield;
}
}
```
AuxiliaryNode
-------------
Εάν δημιουργείτε μια νέα ετικέτα για το Latte, είναι σκόπιμο να δημιουργήσετε μια ειδική κλάση κόμβου για αυτήν, η οποία θα την αντιπροσωπεύει στο δέντρο AST (βλέπε την κλάση `ForeachNode` στο παραπάνω παράδειγμα). Σε ορισμένες περιπτώσεις, μπορεί να βρείτε χρήσιμη την τετριμμένη βοηθητική κλάση κόμβου [AuxiliaryNode |api:Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode], η οποία σας επιτρέπει να περάσετε το σώμα της μεθόδου `print()` και τη λίστα των κόμβων που γίνονται προσβάσιμοι από τη μέθοδο `getIterator()` ως παραμέτρους του κατασκευαστή:
```php
// Latte\Compiler\Nodes\Php\Expression\AuxiliaryNode
// or Latte\Compiler\Nodes\AuxiliaryNode
$node = new AuxiliaryNode(
// body of the print() method:
fn(PrintContext $context, $argNode) => $context->format('myFunc(%node)', $argNode),
// nodes accessed via getIterator() and also passed into the print() method:
[$argNode],
);
```
Μεταγλωττιστής περνάει .[#toc-compiler-passes]
==============================================
Τα περάσματα μεταγλωττιστή είναι συναρτήσεις που τροποποιούν τα AST ή συλλέγουν πληροφορίες σε αυτά. Επιστρέφονται από τη μέθοδο [Extension::getPasses() |#getPasses].
Ανιχνευτής κόμβων .[#toc-node-traverser]
----------------------------------------
Ο πιο συνηθισμένος τρόπος για να εργαστούμε με το AST είναι με τη χρήση ενός [api:Latte\Compiler\NodeTraverser]:
```php
use Latte\Compiler\Node;
use Latte\Compiler\NodeTraverser;
$ast = (new NodeTraverser)->traverse(
$ast,
enter: fn(Node $node) => ...,
leave: fn(Node $node) => ...,
);
```
Η συνάρτηση *enter* (δηλαδή ο επισκέπτης) καλείται όταν συναντάται για πρώτη φορά ένας κόμβος, πριν από την επεξεργασία των υποκόμβων του. Η συνάρτηση *leave* καλείται μετά την επίσκεψη όλων των υποκόμβων.
Ένα κοινό μοτίβο είναι ότι η *enter* χρησιμοποιείται για να συλλέξει κάποιες πληροφορίες και στη συνέχεια η *leave* εκτελεί τροποποιήσεις με βάση αυτές. Τη στιγμή που καλείται η *leave*, όλος ο κώδικας μέσα στον κόμβο θα έχει ήδη επισκεφθεί και θα έχουν συλλεχθεί οι απαραίτητες πληροφορίες.
Πώς να τροποποιήσετε το AST; Ο ευκολότερος τρόπος είναι απλά να αλλάξετε τις ιδιότητες των κόμβων. Ο δεύτερος τρόπος είναι η πλήρης αντικατάσταση του κόμβου επιστρέφοντας έναν νέο κόμβο. Παράδειγμα: ο παρακάτω κώδικας θα αλλάξει όλους τους ακέραιους αριθμούς στο AST σε συμβολοσειρές (π.χ. το 42 θα αλλάξει σε `'42'`).
```php
use Latte\Compiler\Nodes\Php;
$ast = (new NodeTraverser)->traverse(
$ast,
leave: function (Node $node) {
if ($node instanceof Php\Scalar\IntegerNode) {
return new Php\Scalar\StringNode((string) $node->value);
}
},
);
```
Ένα AST μπορεί εύκολα να περιέχει χιλιάδες κόμβους και η διάσχιση όλων αυτών μπορεί να είναι αργή. Σε ορισμένες περιπτώσεις, είναι δυνατόν να αποφευχθεί η πλήρης διάσχιση.
Αν ψάχνετε για όλους τους κόμβους `Html\ElementNode` σε ένα δέντρο, ξέρετε ότι αφού έχετε δει το `Php\ExpressionNode`, δεν υπάρχει λόγος να ελέγξετε επίσης όλους τους κόμβους-παιδιά του, επειδή η HTML δεν μπορεί να είναι μέσα σε εκφράσεις. Σε αυτή την περίπτωση, μπορείτε να δώσετε εντολή στον περιηγητή να μην κάνει αναδρομή στον κόμβο κλάσης:
```php
$ast = (new NodeTraverser)->traverse(
$ast,
enter: function (Node $node) {
if ($node instanceof Php\ExpressionNode) {
return NodeTraverser::DontTraverseChildren;
}
// ...
},
);
```
Αν ψάχνετε μόνο για έναν συγκεκριμένο κόμβο, είναι επίσης δυνατό να διακόψετε εντελώς την αναστροφή αφού τον βρείτε.
```php
$ast = (new NodeTraverser)->traverse(
$ast,
enter: function (Node $node) {
if ($node instanceof Nodes\ParametersNode) {
return NodeTraverser::StopTraversal;
}
// ...
},
);
```
Βοηθοί κόμβων .[#toc-node-helpers]
----------------------------------
Η κλάση [api:Latte\Compiler\NodeHelpers] παρέχει ορισμένες μεθόδους που μπορούν να βρουν AST κόμβους που είτε ικανοποιούν ένα συγκεκριμένο callback κ.λπ. Παρουσιάζονται μερικά παραδείγματα:
```php
use Latte\Compiler\NodeHelpers;
// βρίσκει όλους τους κόμβους στοιχείων HTML
$elements = NodeHelpers::find($ast, fn(Node $node) => $node instanceof Nodes\Html\ElementNode);
// βρίσκει τον πρώτο κόμβο κειμένου
$text = NodeHelpers::findFirst($ast, fn(Node $node) => $node instanceof Nodes\TextNode);
// μετατρέπει τον κόμβο τιμών PHP σε πραγματική τιμή
$value = NodeHelpers::toValue($node);
// μετατρέπει στατικό κόμβο κειμένου σε συμβολοσειρά
$text = NodeHelpers::toText($node);
```