Δημιουργία μιας επέκτασης

Μια επέκταση είναι μια επαναχρησιμοποιήσιμη κλάση που μπορεί να ορίσει προσαρμοσμένες ετικέτες, φίλτρα, λειτουργίες, παρόχους κ.λπ.

Δημιουργούμε επεκτάσεις όταν θέλουμε να επαναχρησιμοποιήσουμε τις προσαρμογές μας στο Latte σε διαφορετικά έργα ή να τις μοιραστούμε με άλλους. Είναι επίσης χρήσιμο να δημιουργήσετε μια επέκταση για κάθε έργο ιστού που θα περιέχει όλες τις συγκεκριμένες ετικέτες και φίλτρα που θέλετε να χρησιμοποιήσετε στα πρότυπα του έργου.

Κλάση επέκτασης

Η Extension είναι μια κλάση που κληρονομεί από την Latte\Extension. Καταχωρείται στο Latte χρησιμοποιώντας την addExtension() (ή μέσω του αρχείου ρυθμίσεων):

$latte = new Latte\Engine;
$latte->addExtension(new MyLatteExtension);

Εάν καταχωρήσετε πολλαπλές επεκτάσεις και ορίζουν ετικέτες, φίλτρα ή λειτουργίες με το ίδιο όνομα, κερδίζει η επέκταση που προστέθηκε τελευταία. Αυτό σημαίνει επίσης ότι οι επεκτάσεις σας μπορούν να παρακάμπτουν τις εγγενείς ετικέτες/φίλτρα/λειτουργίες.

Κάθε φορά που κάνετε μια αλλαγή σε μια κλάση και η αυτόματη ανανέωση δεν είναι απενεργοποιημένη, το Latte θα μεταγλωττίζει αυτόματα ξανά τα πρότυπα σας.

Μια κλάση μπορεί να υλοποιεί οποιαδήποτε από τις ακόλουθες μεθόδους:

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/…xtension.php”.

beforeCompile(Latte\Engine $engine)void

Καλείται πριν από τη μεταγλώττιση του προτύπου. Η μέθοδος μπορεί να χρησιμοποιηθεί για αρχικοποιήσεις που σχετίζονται με τη μεταγλώττιση, για παράδειγμα.

getTags(): array

Καλείται κατά τη μεταγλώττιση του προτύπου. Επιστρέφει έναν συσχετιστικό πίνακα όνομα ετικέτας ⇒ callable, οι οποίες είναι συναρτήσεις ανάλυσης ετικέτας.

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 καθορίζει ποιες ετικέτες διατάσσονται πριν ή μετά την ετικέτα.

public function getTags(): array
{
	return [
		'foo' => self::order([FooNode::class, 'create'], before: 'bar')]
		'bar' => self::order([BarNode::class, 'create'], after: ['block', 'snippet'])]
	];
}

getPasses(): array

Καλείται κατά τη μεταγλώττιση του προτύπου. Επιστρέφει έναν συσχετιστικό πίνακα name pass ⇒ callable, οι οποίες είναι συναρτήσεις που αντιπροσωπεύουν τα λεγόμενα περάσματα του μεταγλωττιστή που διασχίζουν και τροποποιούν το AST.

Και πάλι μπορεί να χρησιμοποιηθεί η βοηθητική μέθοδος order(). Η τιμή των παραμέτρων before ή after μπορεί να είναι * με την έννοια πριν/μετά από όλα.

public function getPasses(): array
{
	return [
		'optimize' => [Passes::class, 'optimizePass'],
		'sandbox' => self::order([$this, 'sandboxPass'], before: '*'),
		// ...
	];
}

beforeRender(Latte\Engine $engine)void

Καλείται πριν από κάθε απόδοση προτύπου. Η μέθοδος μπορεί να χρησιμοποιηθεί, για παράδειγμα, για την αρχικοποίηση μεταβλητών που χρησιμοποιούνται κατά την απόδοση.

getFilters(): array

Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει τα φίλτρα ως συσχετιστικό πίνακα όνομα φίλτρου ⇒ callable.

public function getFilters(): array
{
	return [
		'batch' => [$this, 'batchFilter'],
		'trim' => [$this, 'trimFilter'],
		// ...
	];
}

getFunctions(): array

Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει τις συναρτήσεις ως συσχετιστικό πίνακα όνομα συνάρτησης ⇒ callable.

public function getFunctions(): array
{
	return [
		'clamp' => [$this, 'clampFunction'],
		'divisibleBy' => [$this, 'divisibleByFunction'],
		// ...
	];
}

getProviders(): array

Καλείται πριν από την απόδοση του προτύπου. Επιστρέφει έναν πίνακα παρόχων, οι οποίοι είναι συνήθως αντικείμενα που χρησιμοποιούν ετικέτες κατά την εκτέλεση. Η πρόσβαση σε αυτούς γίνεται μέσω του $this->global->....

public function getProviders(): array
{
	return [
		'myFoo' => $this->foo,
		'myBar' => $this->bar,
		// ...
	];
}

getCacheKey(Latte\Engine $engine)mixed

Καλείται πριν από την απόδοση του προτύπου. Η τιμή επιστροφής γίνεται μέρος του κλειδιού του οποίου ο κατακερματισμός περιέχεται στο όνομα του μεταγλωττισμένου αρχείου προτύπου. Έτσι, για διαφορετικές τιμές επιστροφής, η Latte θα δημιουργήσει διαφορετικά αρχεία κρυφής μνήμης.

Πώς λειτουργεί η Latte;

Για να καταλάβετε πώς να ορίσετε προσαρμοσμένες ετικέτες ή περάσματα μεταγλωττιστή, είναι απαραίτητο να καταλάβετε πώς λειτουργεί το Latte κάτω από την κουκούλα.

Η μεταγλώττιση προτύπων στο Latte απλουστευτικά λειτουργεί ως εξής:

  • Πρώτον, ο lexer μετατρέπει τον πηγαίο κώδικα του προτύπου σε μικρά κομμάτια (tokens) για ευκολότερη επεξεργασία.
  • Στη συνέχεια, ο διαχωριστής μετατρέπει τη ροή των tokens σε ένα ουσιαστικό δέντρο κόμβων (το Αφηρημένο Συντακτικό Δέντρο, AST).
  • Τέλος, ο μεταγλωττιστής δημιουργεί μια κλάση PHP από το AST που αποδίδει το πρότυπο και το αποθηκεύει.

Στην πραγματικότητα, η μεταγλώττιση είναι λίγο πιο περίπλοκη. Το Latte έχει δύο λεξικογράφους και αναλυτές: έναν για το πρότυπο HTML και έναν για τον κώδικα που μοιάζει με PHP μέσα στις ετικέτες. Επίσης, η ανάλυση δεν εκτελείται μετά τη tokenization, αλλά ο lexer και ο parser τρέχουν παράλληλα σε δύο “νήματα” και συντονίζονται. Είναι επιστήμη πυραύλων :-)

Επιπλέον, όλες οι ετικέτες έχουν τις δικές τους ρουτίνες ανάλυσης. Όταν ο αναλυτής συναντά μια ετικέτα, καλεί τη συνάρτηση ανάλυσης της (επιστρέφει την Extension::getTags()). Η δουλειά τους είναι να αναλύσουν τα ορίσματα της ετικέτας και, στην περίπτωση των ζευγαρωμένων ετικετών, το εσωτερικό περιεχόμενο. Επιστρέφει έναν κόμβο που γίνεται μέρος του AST. Ανατρέξτε στη λειτουργία ανάλυσης ετικετών για λεπτομέρειες.

Όταν ο αναλυτής ολοκληρώσει τη δουλειά του, έχουμε ένα πλήρες AST που αναπαριστά το πρότυπο. Ο κόμβος ρίζα είναι το Latte\Compiler\Nodes\TemplateNode. Στη συνέχεια, οι επιμέρους κόμβοι μέσα στο δέντρο αντιπροσωπεύουν όχι μόνο τις ετικέτες, αλλά και τα στοιχεία HTML, τα χαρακτηριστικά τους, τυχόν εκφράσεις που χρησιμοποιούνται μέσα στις ετικέτες κ.λπ.

Στη συνέχεια, μπαίνουν στο παιχνίδι τα λεγόμενα περάσματα του μεταγλωττιστή, τα οποία είναι συναρτήσεις (που επιστρέφονται από την Extension::getPasses()) που τροποποιούν το AST.

Όλη η διαδικασία, από τη φόρτωση του περιεχομένου του προτύπου, μέσω της ανάλυσης, μέχρι τη δημιουργία του αρχείου που προκύπτει, μπορεί να ακολουθηθεί με αυτόν τον κώδικα, με τον οποίο μπορείτε να πειραματιστείτε και να απορρίψετε τα ενδιάμεσα αποτελέσματα:

$latte = new Latte\Engine;
$source = $latte->getLoader()->getContent($file);
$ast = $latte->parse($source);
$latte->applyPasses($ast);
$code = $latte->generate($ast, $file);

Παράδειγμα AST

Για να έχουμε μια καλύτερη ιδέα του AST, προσθέτουμε ένα δείγμα. Αυτό είναι το πρότυπο πηγής:

{foreach $category->getItems() as $item}
	<li>{$item->name|upper}</li>
	{else}
	no items found
{/foreach}

Και αυτή είναι η αναπαράστασή του με τη μορφή AST:

Latte\Compiler\Nodes\TemplateNode(
   Latte\Compiler\Nodes\FragmentNode(
      - Latte\Essential\Nodes\ForeachNode(
           expression: Latte\Compiler\Nodes\Php\Expression\MethodCallNode(
              object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$category')
              name: Latte\Compiler\Nodes\Php\IdentifierNode('getItems')
           )
           value: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item')
           content: Latte\Compiler\Nodes\FragmentNode(
              - Latte\Compiler\Nodes\TextNode('  ')
              - Latte\Compiler\Nodes\Html\ElementNode('li')(
                   content: Latte\Essential\Nodes\PrintNode(
                      expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode(
                         object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item')
                         name: Latte\Compiler\Nodes\Php\IdentifierNode('name')
                      )
                      modifier: Latte\Compiler\Nodes\Php\ModifierNode(
                         filters:
                            - Latte\Compiler\Nodes\Php\FilterNode('upper')
                      )
                   )
                )
            )
            else: Latte\Compiler\Nodes\FragmentNode(
               - Latte\Compiler\Nodes\TextNode('no items found')
            )
        )
   )
)

Προσαρμοσμένες ετικέτες

Τρία βήματα απαιτούνται για τον ορισμό μιας νέας ετικέτας:

Λειτουργία ανάλυσης ετικέτας

Η ανάλυση των ετικετών γίνεται από τη συνάρτηση ανάλυσης (αυτή που επιστρέφεται από την Extension::getTags()). Η δουλειά της είναι να αναλύει και να ελέγχει τυχόν ορίσματα μέσα στην ετικέτα (χρησιμοποιεί τον TagParser για να το κάνει αυτό). Επιπλέον, αν η ετικέτα είναι ένα ζεύγος, θα ζητήσει από τον TemplateParser να αναλύσει και να επιστρέψει το εσωτερικό περιεχόμενο. Η συνάρτηση δημιουργεί και επιστρέφει έναν κόμβο, ο οποίος είναι συνήθως παιδί του Latte\Compiler\Nodes\StatementNode, και αυτός γίνεται μέρος του AST.

Δημιουργούμε μια κλάση για κάθε κόμβο, κάτι που θα κάνουμε τώρα, και τοποθετούμε κομψά τη συνάρτηση ανάλυσης σε αυτήν ως στατικό εργοστάσιο. Ως παράδειγμα, ας δοκιμάσουμε να δημιουργήσουμε τη γνωστή ετικέτα {foreach}:

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() περνάει ένα αντικείμενο Latte\Compiler\Tag, το οποίο μεταφέρει βασικές πληροφορίες για την ετικέτα (αν είναι κλασική ετικέτα ή n:attribute, σε ποια γραμμή βρίσκεται, κ.λπ.) και κυρίως προσπελαύνει το 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[])

και ένα χαμηλού επιπέδου 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() χρησιμοποιείται για την ανάλυσή του.

Η μελέτη των κλάσεων κόμβων που αποτελούν μέρος του Latte είναι ο καλύτερος τρόπος για να μάθετε όλες τις μικρές λεπτομέρειες της διαδικασίας ανάλυσης.

Ας επιστρέψουμε στην ετικέτα {foreach}. Σε αυτήν, περιμένουμε ορίσματα της μορφής expression + 'as' + second expression, τα οποία αναλύουμε ως εξής:

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 αντιπροσωπεύουν υποκόμβους.

Ορίστε τις μεταβλητές με υποκόμβους ως δημόσιες, ώστε να μπορούν να τροποποιηθούν σε περαιτέρω βήματα επεξεργασίας, αν χρειαστεί. Είναι επίσης απαραίτητο να διαθέσουμε τις μεταβλητές αυτές για διάσχιση.

Για ζευγαρωτές ετικέτες, όπως η δική μας, η μέθοδος πρέπει επίσης να επιτρέπει στον TemplateParser να αναλύει τα εσωτερικά περιεχόμενα της ετικέτας. Αυτό αντιμετωπίζεται από το yield, το οποίο επιστρέφει ένα ζεύγος [εσωτερικό περιεχόμενο, τέλος ετικέτας]. Αποθηκεύουμε το εσωτερικό περιεχόμενο στη μεταβλητή $node->content.

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:

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

Κάθε κόμβος πρέπει να υλοποιεί τη μέθοδο print(). Επιστρέφει κώδικα PHP που αποδίδει το συγκεκριμένο τμήμα του προτύπου (κώδικας εκτέλεσης). Παραδίδεται ένα αντικείμενο Latte\Compiler\PrintContext ως παράμετρος, το οποίο διαθέτει μια χρήσιμη μέθοδο format() που απλοποιεί τη συναρμολόγηση του κώδικα που προκύπτει.

Η μέθοδος format(string $mask, ...$args) δέχεται τα ακόλουθα placeholders στη μάσκα:

  • %node εκτυπώνει τον κόμβο
  • %dump εξάγει την τιμή στην PHP
  • %raw εισάγει το κείμενο απευθείας χωρίς μετασχηματισμό
  • %args εκτυπώνει ArrayNode ως ορίσματα στην κλήση της συνάρτησης
  • %line εκτυπώνει ένα σχόλιο με αριθμό γραμμής
  • %escape(...) διαφυγή του περιεχομένου
  • %modify(...) εφαρμόζει τροποποιητή
  • %modifyContent(...) εφαρμόζει τροποποιητή σε μπλοκ

Η συνάρτηση print() θα μπορούσε να μοιάζει ως εξής (παραλείπουμε τον κλάδο else για λόγους απλότητας):

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 έχει ήδη οριστεί από την κλάση Latte\Compiler\Node και ορίζεται από τον αναλυτή. Περιέχει ένα αντικείμενο Latte\Compiler\Position με τη θέση της ετικέτας στον πηγαίο κώδικα με τη μορφή αριθμού γραμμής και στήλης.

Ο κώδικας εκτέλεσης μπορεί να χρησιμοποιεί βοηθητικές μεταβλητές. Για να αποφευχθεί η σύγκρουση με τις μεταβλητές που χρησιμοποιούνται από το ίδιο το πρότυπο, είναι σύμβαση να τους προτάσσεται με χαρακτήρες $ʟ__.

Μπορεί επίσης να χρησιμοποιεί αυθαίρετες τιμές κατά την εκτέλεση, οι οποίες περνούν στο πρότυπο με τη μορφή παρόχων χρησιμοποιώντας τη μέθοδο Extension::getProviders(). Η πρόσβαση σε αυτές γίνεται με τη χρήση του $this->global->....

Διασχίζοντας το AST

Για να διασχίσουμε το δέντρο AST σε βάθος, είναι απαραίτητο να υλοποιήσουμε τη μέθοδο getIterator(). Αυτό θα παρέχει πρόσβαση σε υποκόμβους:

public function &getIterator(): \Generator
{
	yield $this->expression;
	yield $this->value;
	yield $this->content;
	if ($this->elseContent) {
		yield $this->elseContent;
	}
}

Σημειώστε ότι η getIterator() επιστρέφει μια αναφορά. Αυτό είναι που επιτρέπει στους επισκέπτες των κόμβων να αντικαταστήσουν μεμονωμένους κόμβους με άλλους κόμβους.

Εάν ένας κόμβος έχει υποκόμβους, είναι απαραίτητο να υλοποιήσετε αυτή τη μέθοδο και να καταστήσετε όλους τους υποκόμβους διαθέσιμους. Διαφορετικά, θα μπορούσε να δημιουργηθεί ένα κενό ασφαλείας. Για παράδειγμα, η λειτουργία sandbox δεν θα μπορούσε να ελέγξει τους υποκόμβους και να διασφαλίσει ότι δεν θα καλούνται σε αυτούς μη επιτρεπόμενες κατασκευές.

Δεδομένου ότι η λέξη-κλειδί yield πρέπει να υπάρχει στο σώμα της μεθόδου, ακόμη και αν δεν έχει κόμβους-παιδιά, γράψτε την ως εξής:

public function &getIterator(): \Generator
{
	if (false) {
		yield;
	}
}

AuxiliaryNode

Εάν δημιουργείτε μια νέα ετικέτα για το Latte, είναι σκόπιμο να δημιουργήσετε μια ειδική κλάση κόμβου για αυτήν, η οποία θα την αντιπροσωπεύει στο δέντρο AST (βλέπε την κλάση ForeachNode στο παραπάνω παράδειγμα). Σε ορισμένες περιπτώσεις, μπορεί να βρείτε χρήσιμη την τετριμμένη βοηθητική κλάση κόμβου AuxiliaryNode, η οποία σας επιτρέπει να περάσετε το σώμα της μεθόδου print() και τη λίστα των κόμβων που γίνονται προσβάσιμοι από τη μέθοδο getIterator() ως παραμέτρους του κατασκευαστή:

// 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],
);

Μεταγλωττιστής περνάει

Τα περάσματα μεταγλωττιστή είναι συναρτήσεις που τροποποιούν τα AST ή συλλέγουν πληροφορίες σε αυτά. Επιστρέφονται από τη μέθοδο Extension::getPasses().

Ανιχνευτής κόμβων

Ο πιο συνηθισμένος τρόπος για να εργαστούμε με το AST είναι με τη χρήση ενός Latte\Compiler\NodeTraverser:

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').

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 δεν μπορεί να είναι μέσα σε εκφράσεις. Σε αυτή την περίπτωση, μπορείτε να δώσετε εντολή στον περιηγητή να μην κάνει αναδρομή στον κόμβο κλάσης:

$ast = (new NodeTraverser)->traverse(
	$ast,
	enter: function (Node $node) {
		if ($node instanceof Php\ExpressionNode) {
			return NodeTraverser::DontTraverseChildren;
        }
        // ...
	},
);

Αν ψάχνετε μόνο για έναν συγκεκριμένο κόμβο, είναι επίσης δυνατό να διακόψετε εντελώς την αναστροφή αφού τον βρείτε.

$ast = (new NodeTraverser)->traverse(
	$ast,
	enter: function (Node $node) {
		if ($node instanceof Nodes\ParametersNode) {
			return NodeTraverser::StopTraversal;
        }
        // ...
	},
);

Βοηθοί κόμβων

Η κλάση Latte\Compiler\NodeHelpers παρέχει ορισμένες μεθόδους που μπορούν να βρουν AST κόμβους που είτε ικανοποιούν ένα συγκεκριμένο callback κ.λπ. Παρουσιάζονται μερικά παραδείγματα:

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);
έκδοση: 3.0