Επέκταση Latte

Το Latte είναι πολύ ευέλικτο και μπορεί να επεκταθεί με πολλούς τρόπους: μπορείτε να προσθέσετε προσαρμοσμένα φίλτρα, συναρτήσεις, ετικέτες, φορτωτές κ.λπ. Θα σας δείξουμε πώς να το κάνετε.

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

Πόσοι δρόμοι οδηγούν στη Ρώμη;

Δεδομένου ότι ορισμένοι από τους τρόπους επέκτασης του Latte μπορούν να αναμειχθούν, ας προσπαθήσουμε πρώτα να εξηγήσουμε τις διαφορές μεταξύ τους. Ως παράδειγμα, ας προσπαθήσουμε να υλοποιήσουμε μια γεννήτρια Lorem ipsum, στην οποία δίνεται ο αριθμός των λέξεων που πρέπει να παραχθεί.

Η κύρια κατασκευή της γλώσσας Latte είναι η ετικέτα. Μπορούμε να υλοποιήσουμε μια γεννήτρια επεκτείνοντας τη Latte με μια νέα ετικέτα:

{lipsum 40}

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

Εντάξει, ας δοκιμάσουμε να δημιουργήσουμε ένα φίλτρο αντί για μια ετικέτα:

{=40|lipsum}

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

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

{lipsum(40)}

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

{var $text = lipsum(40)}

Φίλτρα

Δημιουργήστε ένα φίλτρο καταχωρώντας το όνομά του και οποιαδήποτε δυνατότητα κλήσης της PHP, όπως μια συνάρτηση:

$latte = new Latte\Engine;
$latte->addFilter('shortify', fn(string $s) => mb_substr($s, 0, 10)); // συντομεύει το κείμενο σε 10 χαρακτήρες

Σε αυτή την περίπτωση θα ήταν καλύτερο για το φίλτρο να λαμβάνει μια πρόσθετη παράμετρο:

$latte->addFilter('shortify', fn(string $s, int $len = 10) => mb_substr($s, 0, $len));

Το χρησιμοποιούμε σε ένα πρότυπο όπως αυτό:

<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, ο άλλος τρόπος είναι τα φίλτρα πλαισίου.

$latte->addFilter('money', fn(float $amount) => new Latte\Runtime\Html("<i>$amount EUR</i>"));

Στην περίπτωση αυτή, το φίλτρο πρέπει να διασφαλίζει τη σωστή διαφυγή των δεδομένων.

Φίλτρα που χρησιμοποιούν την κλάση

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

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);

Φορτωτής φίλτρων

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

$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);
	}

	// ...
}

Φίλτρα πλαισίου

Ένα φίλτρο πλαισίου είναι ένα φίλτρο που δέχεται ένα αντικείμενο Latte\Runtime\FilterInfo στην πρώτη παράμετρο, ακολουθούμενο από άλλες παραμέτρους όπως στην περίπτωση των κλασικών φίλτρων. Καταχωρείται με τον ίδιο τρόπο, το ίδιο το Latte αναγνωρίζει ότι το φίλτρο είναι contextual:

use Latte\Runtime\FilterInfo;

$latte->addFilter('foo', function (FilterInfo $info, string $str): string {
	// ...
});

Τα φίλτρα πλαισίου μπορούν να ανιχνεύσουν και να αλλάξουν τον τύπο περιεχομένου που λαμβάνουν στη μεταβλητή $info->contentType. Αν το φίλτρο κληθεί κλασικά πάνω σε μια μεταβλητή (π.χ. {$var|foo}), το $info->contentType θα περιέχει null.

Το φίλτρο θα πρέπει πρώτα να ελέγξει αν ο τύπος περιεχομένου της συμβολοσειράς εισόδου υποστηρίζεται. Μπορεί επίσης να τον αλλάξει. Παράδειγμα ενός φίλτρου που δέχεται κείμενο (ή null) και επιστρέφει HTML:

use Latte\Runtime\FilterInfo;

$latte->addFilter('money', function (FilterInfo $info, float $amount): string {
	// πρώτα ελέγχουμε αν ο τύπος περιεχομένου της εισόδου είναι text
	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>";
});

Σε αυτή την περίπτωση, το φίλτρο πρέπει να διασφαλίσει τη σωστή διαφυγή των δεδομένων.

Όλα τα φίλτρα που χρησιμοποιούνται πάνω από μπλοκ (π.χ. ως {block|foo}...{/block}) πρέπει να είναι συμφραζόμενα.

Συναρτήσεις

Από προεπιλογή, όλες οι εγγενείς συναρτήσεις PHP μπορούν να χρησιμοποιηθούν στο Latte, εκτός αν το sandbox το απενεργοποιήσει. Αλλά μπορείτε επίσης να ορίσετε τις δικές σας συναρτήσεις. Αυτές μπορούν να αντικαταστήσουν τις εγγενείς συναρτήσεις.

Δημιουργήστε μια συνάρτηση καταγράφοντας το όνομά της και οποιαδήποτε δυνατότητα κλήσης της PHP:

$latte = new Latte\Engine;
$latte->addFunction('random', function (...$args) {
	return $args[array_rand($args)];
});

Η χρήση είναι τότε η ίδια με την κλήση της συνάρτησης PHP:

{random(apple, orange, lemon)} // prints for example: apple

Συναρτήσεις που χρησιμοποιούν την κλάση

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

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);

Φορτωτές

Οι φορτωτές είναι υπεύθυνοι για τη φόρτωση προτύπων από μια πηγή, όπως ένα σύστημα αρχείων. Ορίζονται χρησιμοποιώντας τη μέθοδο setLoader():

$latte->setLoader(new MyLoader);

Οι ενσωματωμένοι φορτωτές είναι οι εξής:

FileLoader

Προεπιλεγμένος φορτωτής. Φορτώνει πρότυπα από το σύστημα αρχείων.

Η πρόσβαση στα αρχεία μπορεί να περιοριστεί με τον καθορισμό του βασικού καταλόγου:

$latte->setLoader(new Latte\Loaders\FileLoader($templateDir));
$latte->render('test.latte');

StringLoader

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

$latte->setLoader(new Latte\Loaders\StringLoader([
	'main.file' => '{include other.file}',
	'other.file' => '{if true} {$var} {/if}',
]));

$latte->render('main.file');

Απλοποιημένη χρήση:

$template = '{if true} {$var} {/if}';
$latte->setLoader(new Latte\Loaders\StringLoader);
$latte->render($template);

Δημιουργία ενός προσαρμοσμένου φορτωτή

Ο φορτωτής είναι μια κλάση που υλοποιεί τη διεπαφή Latte\Loader.

Ετικέτες

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

Στις περισσότερες περιπτώσεις, ωστόσο, η ετικέτα δεν είναι απαραίτητη:

  • αν πρέπει να παράγει κάποια έξοδο, χρησιμοποιήστε αντ' αυτού τη συνάρτηση
  • αν επρόκειτο να τροποποιήσει κάποια είσοδο και να την επιστρέψει, χρησιμοποιήστε αντί αυτού filter
  • αν επρόκειτο να επεξεργαστεί μια περιοχή κειμένου, τυλίξτε την με ένα {block} ετικέτα και χρησιμοποιήστε ένα φίλτρο
  • αν δεν έπρεπε να παράγει κάτι αλλά απλώς να καλεί μια συνάρτηση, καλέστε την με την εντολή {do}

Αν εξακολουθείτε να θέλετε να δημιουργήσετε μια ετικέτα, τέλεια! Όλα τα βασικά στοιχεία μπορείτε να τα βρείτε στην ενότητα Δημιουργία μιας επέκτασης.

Περάσματα μεταγλωττιστή

Τα περάσματα μεταγλωττιστή είναι συναρτήσεις που τροποποιούν τα AST ή συλλέγουν πληροφορίες σε αυτά. Στο Latte, για παράδειγμα, ένα sandbox υλοποιείται με αυτόν τον τρόπο: διατρέχει όλους τους κόμβους ενός AST, βρίσκει κλήσεις συναρτήσεων και μεθόδων και τις αντικαθιστά με ελεγχόμενες κλήσεις.

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

έκδοση: 3.0