Δρομολόγηση

Ο Router είναι υπεύθυνος για τα πάντα γύρω από τις διευθύνσεις URL, ώστε να μην χρειάζεται πλέον να τις σκέφτεστε. Θα δείξουμε:

  • πώς να ρυθμίσετε τον router ώστε τα URL να είναι όπως τα φαντάζεστε
  • θα μιλήσουμε για SEO και ανακατεύθυνση
  • και θα δείξουμε πώς να γράψετε τον δικό σας router

Οι πιο ανθρώπινες διευθύνσεις URL (ή αλλιώς cool ή pretty URL) είναι πιο εύχρηστες, πιο εύκολα απομνημονεύσιμες και συμβάλλουν θετικά στο SEO. Το Nette το λαμβάνει υπόψη και υποστηρίζει πλήρως τους προγραμματιστές. Μπορείτε να σχεδιάσετε για την εφαρμογή σας ακριβώς τη δομή των διευθύνσεων URL που θέλετε. Μπορείτε να τη σχεδιάσετε ακόμη και όταν η εφαρμογή είναι ήδη έτοιμη, επειδή αυτό γίνεται χωρίς παρεμβάσεις στον κώδικα ή τα πρότυπα. Ορίζεται με κομψό τρόπο σε ένα μόνο σημείο, στον router, και δεν είναι διάσπαρτη με τη μορφή σχολιαστικών παρατηρήσεων σε όλους τους presenters.

Ο router στο Nette είναι εξαιρετικός στο ότι είναι αμφίδρομος. Μπορεί τόσο να αποκωδικοποιεί τα URL σε αιτήματα HTTP, όσο και να δημιουργεί συνδέσμους. Παίζει επομένως καθοριστικό ρόλο στην Nette Application, επειδή αφενός αποφασίζει ποιος presenter και action θα εκτελέσει το τρέχον αίτημα, αλλά χρησιμοποιείται επίσης για τη δημιουργία URL στο πρότυπο κ.λπ.

Ωστόσο, ο router δεν περιορίζεται μόνο σε αυτή τη χρήση, μπορείτε να τον χρησιμοποιήσετε σε εφαρμογές όπου δεν χρησιμοποιούνται καθόλου presenters, για REST API, κ.λπ. Περισσότερα στην ενότητα samostatné použití.

Συλλογή διαδρομών

Ο πιο ευχάριστος τρόπος για να ορίσετε τη μορφή των διευθύνσεων URL στην εφαρμογή είναι η κλάση Nette\Application\Routers\RouteList. Ο ορισμός αποτελείται από μια λίστα λεγόμενων routes, δηλαδή μασκών διευθύνσεων URL και των σχετικών presenters και actions που συνδέονται με αυτές μέσω ενός απλού API. Δεν χρειάζεται να ονομάσουμε τις routes με κανέναν τρόπο.

$router = new Nette\Application\Routers\RouteList;
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('article/<id>', 'Article:view');
// ...

Το παράδειγμα λέει ότι αν ανοίξουμε στο πρόγραμμα περιήγησης το https://domain.com/rss.xml, θα εμφανιστεί ο presenter Feed με την action rss, αν ανοίξουμε το https://domain.com/article/12, θα εμφανιστεί ο presenter Article με την action view κ.λπ. Σε περίπτωση που δεν βρεθεί κατάλληλη route, η Nette Application αντιδρά δημιουργώντας την εξαίρεση BadRequestException, η οποία εμφανίζεται στον χρήστη ως σελίδα σφάλματος 404 Not Found.

Σειρά διαδρομών

Η σειρά με την οποία αναφέρονται οι επιμέρους routes είναι απολύτως κρίσιμη, επειδή αξιολογούνται διαδοχικά από πάνω προς τα κάτω. Ισχύει ο κανόνας ότι δηλώνουμε τις routes από τις πιο συγκεκριμένες προς τις πιο γενικές:

// ΛΑΘΟΣ: το 'rss.xml' πιάνεται από την πρώτη route και κατανοεί αυτή τη συμβολοσειρά ως <slug>
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

// ΣΩΣΤΟ
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');

Οι routes αξιολογούνται από πάνω προς τα κάτω και κατά τη δημιουργία συνδέσμων:

// ΛΑΘΟΣ: ο σύνδεσμος προς 'Feed:rss' δημιουργείται ως 'admin/feed/rss'
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

// ΣΩΣΤΟ
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');

Δεν θα κρύψουμε από εσάς ότι η σωστή σύνθεση των routes απαιτεί κάποια δεξιότητα. Μέχρι να την αποκτήσετε, θα σας φανεί χρήσιμος ο πίνακας δρομολόγησης.

Μάσκα και παράμετροι

Η μάσκα περιγράφει τη σχετική διαδρομή από τον ριζικό κατάλογο του ιστότοπου. Η απλούστερη μάσκα είναι ένα στατικό URL:

$router->addRoute('products', 'Products:default');

Συχνά οι μάσκες περιέχουν τις λεγόμενες παραμέτρους. Αυτές αναφέρονται σε αιχμηρές αγκύλες (π.χ. <year>) και μεταβιβάζονται στον presenter προορισμού, για παράδειγμα στη μέθοδο renderShow(int $year) ή στην persistent παράμετρο $year:

$router->addRoute('chronicle/<year>', 'History:show');

Το παράδειγμα λέει ότι αν ανοίξουμε στο πρόγραμμα περιήγησης το https://example.com/chronicle/2020, θα εμφανιστεί ο presenter History με την action show και την παράμετρο year: 2020.

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

$router->addRoute('chronicle/<year=2020>', 'History:show');

Η route θα δέχεται τώρα και το URL https://example.com/chronicle/, το οποίο θα εμφανίσει ξανά το History:show με την παράμετρο year: 2020.

Παράμετρος μπορεί φυσικά να είναι και το όνομα του presenter και της action. Για παράδειγμα, έτσι:

$router->addRoute('<presenter>/<action>', 'Home:default');

Η αναφερόμενη route δέχεται, για παράδειγμα, URL της μορφής /article/edit ή επίσης /catalog/list και τα κατανοεί ως presenters και actions Article:edit και Catalog:list.

Ταυτόχρονα, δίνει στις παραμέτρους presenter και action τις προεπιλεγμένες τιμές Home και default και είναι επομένως επίσης προαιρετικές. Έτσι, η route δέχεται και URL της μορφής /article και το κατανοεί ως Article:default. Ή αντίστροφα, ένας σύνδεσμος προς το Product:default θα δημιουργήσει τη διαδρομή /product, ένας σύνδεσμος προς το προεπιλεγμένο Home:default τη διαδρομή /.

Η μάσκα μπορεί να περιγράφει όχι μόνο τη σχετική διαδρομή από τον ριζικό κατάλογο του ιστότοπου, αλλά και την απόλυτη διαδρομή, αν ξεκινά με κάθετο, ή ακόμα και ολόκληρο το απόλυτο URL, αν ξεκινά με δύο κάθετους:

// σχετικά με το document root
$router->addRoute('<presenter>/<action>', /* ... */);

// απόλυτη διαδρομή (σχετικά με τον τομέα)
$router->addRoute('/<presenter>/<action>', /* ... */);

// απόλυτο URL συμπεριλαμβανομένου του τομέα (σχετικά με το σχήμα)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// απόλυτο URL συμπεριλαμβανομένου του σχήματος
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);

Εκφράσεις επικύρωσης

Για κάθε παράμετρο, μπορεί να οριστεί μια συνθήκη επικύρωσης χρησιμοποιώντας μια κανονική έκφραση. Για παράδειγμα, για την παράμετρο id, καθορίζουμε ότι μπορεί να πάρει μόνο αριθμητικές τιμές χρησιμοποιώντας την κανονική έκφραση \d+:

$router->addRoute('<presenter>/<action>[/<id \d+>]', /* ... */);

Η προεπιλεγμένη κανονική έκφραση για όλες τις παραμέτρους είναι [^/]+, δηλαδή οτιδήποτε εκτός από κάθετο. Αν μια παράμετρος πρέπει να δέχεται και κάθετους, καθορίζουμε την έκφραση .+:

// δέχεται https://example.com/a/b/c, η διαδρομή θα είναι 'a/b/c'
$router->addRoute('<path .+>', /* ... */);

Προαιρετικές ακολουθίες

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

$router->addRoute('[<lang [a-z]{2}>/]<name>', /* ... */);

// Δέχεται διαδρομές:
//    /cs/download  => lang => cs, name => download
//    /download     => lang => null, name => download

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

Προαιρετικά τμήματα μπορούν να υπάρχουν και στον τομέα:

$router->addRoute('//[<lang=en>.]example.com/<presenter>/<action>', /* ... */);

Οι ακολουθίες μπορούν να ενσωματωθούν και να συνδυαστούν ελεύθερα:

$router->addRoute(
	'[<lang [a-z]{2}>[-<sublang>]/]<name>[/page-<page=0>]',
	'Home:default',
);

// Δέχεται διαδρομές:
// 	/cs/hello
// 	/en-us/hello
// 	/hello
// 	/hello/page-12

Κατά τη δημιουργία URL, επιδιώκεται η συντομότερη παραλλαγή, οπότε οτιδήποτε μπορεί να παραλειφθεί, παραλείπεται. Γι' αυτό, για παράδειγμα, η route index[.html] δημιουργεί τη διαδρομή /index. Η αναστροφή της συμπεριφοράς είναι δυνατή με την προσθήκη ενός θαυμαστικού μετά την αριστερή αγκύλη:

// δέχεται /hello και /hello.html, δημιουργεί /hello
$router->addRoute('<name>[.html]', /* ... */);

// δέχεται /hello και /hello.html, δημιουργεί /hello.html
$router->addRoute('<name>[!.html]', /* ... */);

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

$router->addRoute('<presenter=Home>/<action=default>/<id=>', /* ... */);

// αντιστοιχεί σε αυτό:
$router->addRoute('[<presenter=Home>/[<action=default>/[<id>]]]', /* ... */);

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

$router->addRoute('[<presenter=Home>[/<action=default>[/<id>]]]', /* ... */);

Χαρακτήρες μπαλαντέρ

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

  • %tld% = top level domain, π.χ. com ή org
  • %sld% = second level domain, π.χ. example
  • %domain% = τομέας χωρίς υποτομείς, π.χ. example.com
  • %host% = ολόκληρος ο host, π.χ. www.example.com
  • %basePath% = διαδρομή προς τον ριζικό κατάλογο
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action', /* ... */);

Εκτεταμένη σημειογραφία

Ο στόχος της route, που συνήθως γράφεται με τη μορφή Presenter:action, μπορεί επίσης να γραφτεί χρησιμοποιώντας έναν πίνακα που ορίζει τις επιμέρους παραμέτρους και τις προεπιλεγμένες τους τιμές:

$router->addRoute('<presenter>/<action>[/<id \d+>]', [
	'presenter' => 'Home',
	'action' => 'default',
]);

Για πιο λεπτομερή προδιαγραφή, μπορεί να χρησιμοποιηθεί μια ακόμη πιο εκτεταμένη μορφή, όπου εκτός από τις προεπιλεγμένες τιμές, μπορούμε να ορίσουμε και άλλες ιδιότητες των παραμέτρων, όπως για παράδειγμα την κανονική έκφραση επικύρωσης (βλ. παράμετρο id):

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>[/<id>]', [
	'presenter' => [
		Route::Value => 'Home',
	],
	'action' => [
		Route::Value => 'default',
	],
	'id' => [
		Route::Pattern => '\d+',
	],
]);

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

Φίλτρα και μεταφράσεις

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

$router->addRoute('<presenter>/<action>', 'Home:default');

θα δημιουργήσει αγγλικά URL, όπως /product/123 ή /cart. Αν θέλουμε οι presenters και οι actions στο URL να αντιπροσωπεύονται από ελληνικές λέξεις (π.χ. /produkt/123 ή /kosik), μπορούμε να χρησιμοποιήσουμε ένα λεξικό μετάφρασης. Για τη σύνταξή του, χρειαζόμαστε ήδη την “πιο ομιλητική” παραλλαγή της δεύτερης παραμέτρου:

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// συμβολοσειρά στο URL => presenter
			'produkt' => 'Product',
			'kosik' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'seznam' => 'list',
		],
	],
]);

Πολλά κλειδιά του λεξικού μετάφρασης μπορούν να οδηγούν στον ίδιο presenter. Έτσι, δημιουργούνται διάφορα ψευδώνυμα γι' αυτόν. Ως κανονική παραλλαγή (δηλαδή αυτή που θα βρίσκεται στο δημιουργημένο URL) θεωρείται το τελευταίο κλειδί.

Ο πίνακας μετάφρασης μπορεί να χρησιμοποιηθεί με αυτόν τον τρόπο για οποιαδήποτε παράμετρο. Ενώ αν η μετάφραση δεν υπάρχει, λαμβάνεται η αρχική τιμή. Αυτή η συμπεριφορά μπορεί να αλλάξει συμπληρώνοντας Route::FilterStrict => true και η route θα απορρίψει τότε το URL αν η τιμή δεν βρίσκεται στο λεξικό.

Εκτός από το λεξικό μετάφρασης με τη μορφή πίνακα, μπορούν να εφαρμοστούν και προσαρμοσμένες συναρτήσεις μετάφρασης.

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>/<id>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterIn => function (string $s): string { /* ... */ },
		Route::FilterOut => function (string $s): string { /* ... */ },
	],
	'action' => 'default',
	'id' => null,
]);

Η συνάρτηση Route::FilterIn μετατρέπει μεταξύ της παραμέτρου στο URL και της συμβολοσειράς που στη συνέχεια μεταβιβάζεται στον presenter, η συνάρτηση FilterOut εξασφαλίζει τη μετατροπή προς την αντίθετη κατεύθυνση.

Οι παράμετροι presenter, action και module έχουν ήδη προκαθορισμένα φίλτρα που μετατρέπουν μεταξύ του στυλ PascalCase ή camelCase και του kebab-case που χρησιμοποιείται στα URL. Η προεπιλεγμένη τιμή των παραμέτρων γράφεται ήδη στη μετασχηματισμένη μορφή, οπότε για παράδειγμα στην περίπτωση του presenter γράφουμε <presenter=ProductEdit>, όχι <presenter=product-edit>.

Γενικά φίλτρα

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

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => 'Home',
	'action' => 'default',
	null => [
		Route::FilterIn => function (array $params): array { /* ... */ },
		Route::FilterOut => function (array $params): array { /* ... */ },
	],
]);

Τα γενικά φίλτρα δίνουν τη δυνατότητα να τροποποιήσετε τη συμπεριφορά της route με απολύτως οποιονδήποτε τρόπο. Μπορούμε να τα χρησιμοποιήσουμε, για παράδειγμα, για την τροποποίηση παραμέτρων με βάση άλλες παραμέτρους. Για παράδειγμα, τη μετάφραση των <presenter> και <action> με βάση την τρέχουσα τιμή της παραμέτρου <lang>.

Αν μια παράμετρος έχει ορισμένο δικό της φίλτρο και ταυτόχρονα υπάρχει ένα γενικό φίλτρο, εκτελείται το δικό της FilterIn πριν από το γενικό και αντίστροφα το γενικό FilterOut πριν από το δικό της. Επομένως, μέσα στο γενικό φίλτρο, οι τιμές των παραμέτρων presenter ή action είναι γραμμένες σε στυλ PascalCase ή camelCase.

Μονόδρομες OneWay

Οι μονόδρομες routes χρησιμοποιούνται για τη διατήρηση της λειτουργικότητας παλιών URL, τα οποία η εφαρμογή δεν δημιουργεί πλέον, αλλά εξακολουθεί να δέχεται. Τις επισημαίνουμε με τη σημαία OneWay:

// παλιό URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY);
// νέο URL /product/123
$router->addRoute('product/<id>', 'Product:detail');

Κατά την πρόσβαση στο παλιό URL, ο presenter ανακατευθύνει αυτόματα στο νέο URL, οπότε οι μηχανές αναζήτησης δεν θα ευρετηριάσουν αυτές τις σελίδες δύο φορές (βλ. SEO a kanonizace).

Δυναμική δρομολόγηση με callbacks

Η δυναμική δρομολόγηση με callbacks σας επιτρέπει να αντιστοιχίσετε απευθείας συναρτήσεις (callbacks) στις routes, οι οποίες εκτελούνται όταν επισκέπτεστε τη συγκεκριμένη διαδρομή. Αυτή η ευέλικτη λειτουργικότητα σας επιτρέπει να δημιουργείτε γρήγορα και αποτελεσματικά διάφορα τελικά σημεία (endpoints) για την εφαρμογή σας:

$router->addRoute('test', function () {
	echo 'βρίσκεστε στη διεύθυνση /test';
});

Μπορείτε επίσης να ορίσετε παραμέτρους στη μάσκα, οι οποίες θα μεταβιβαστούν αυτόματα στο callback σας:

$router->addRoute('<lang cs|en>', function (string $lang) {
	echo match ($lang) {
		'cs' => 'Καλώς ήρθατε στην τσέχικη έκδοση του ιστότοπού μας!',
		'en' => 'Welcome to the English version of our website!',
	};
});

Modules

Αν έχουμε πολλαπλές routes που ανήκουν σε ένα κοινό module, χρησιμοποιούμε το withModule():

$router = new RouteList;
$router->withModule('Forum') // οι ακόλουθες routes είναι μέρος του module Forum
	->addRoute('rss', 'Feed:rss') // ο presenter θα είναι Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // οι ακόλουθες routes είναι μέρος του module Forum:Admin
		->addRoute('sign:in', 'Sign:in');

Μια εναλλακτική είναι η χρήση της παραμέτρου module:

// Το URL manage/dashboard/default αντιστοιχεί στον presenter Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);

Υποτομείς

Μπορούμε να χωρίσουμε τις συλλογές routes ανάλογα με τους υποτομείς:

$router = new RouteList;
$router->withDomain('example.com')
	->addRoute('rss', 'Feed:rss')
	->addRoute('<presenter>/<action>');

Στο όνομα του τομέα, μπορείτε επίσης να χρησιμοποιήσετε zástupné znaky:

$router = new RouteList;
$router->withDomain('example.%tld%')
	// ...

Πρόθεμα διαδρομής

Μπορούμε να χωρίσουμε τις συλλογές routes ανάλογα με τη διαδρομή στο URL:

$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // πιάνει το URL /eshop/rss
	->addRoute('<presenter>/<action>'); // πιάνει το URL /eshop/<presenter>/<action>

Συνδυασμός

Μπορούμε να συνδυάσουμε τις παραπάνω διαρθρώσεις μεταξύ τους:

$router = (new RouteList)
	->withDomain('admin.example.com')
		->withModule('Admin')
			->addRoute(/* ... */)
			->addRoute(/* ... */)
		->end()
		->withModule('Images')
			->addRoute(/* ... */)
		->end()
	->end()
	->withDomain('example.com')
		->withPath('export')
			->addRoute(/* ... */)
			// ...

Παράμετροι Query

Οι μάσκες μπορούν επίσης να περιέχουν παραμέτρους query (παραμέτρους μετά το ερωτηματικό στο URL). Δεν μπορεί να οριστεί γι' αυτές κανονική έκφραση επικύρωσης, αλλά μπορεί να αλλάξει το όνομα με το οποίο μεταβιβάζονται στον presenter:

// θέλουμε να χρησιμοποιήσουμε την παράμετρο query 'cat' στην εφαρμογή με το όνομα 'categoryId'
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);

Παράμετροι Foo

Τώρα πηγαίνουμε βαθύτερα. Οι παράμετροι Foo είναι ουσιαστικά ανώνυμες παράμετροι που επιτρέπουν την αντιστοίχιση μιας κανονικής έκφρασης. Παράδειγμα είναι μια route που δέχεται /index, /index.html, /index.htm και /index.php:

$router->addRoute('index<? \.html?|\.php|>', /* ... */);

Μπορείτε επίσης να ορίσετε ρητά τη συμβολοσειρά που θα χρησιμοποιηθεί κατά τη δημιουργία του URL. Η συμβολοσειρά πρέπει να τοποθετηθεί αμέσως μετά το ερωτηματικό. Η ακόλουθη route είναι παρόμοια με την προηγούμενη, αλλά δημιουργεί /index.html αντί για /index, επειδή η συμβολοσειρά .html έχει οριστεί ως τιμή δημιουργίας:

$router->addRoute('index<?.html \.html?|\.php|>', /* ... */);

Ενσωμάτωση στην εφαρμογή

Για να ενσωματώσουμε τον δημιουργημένο router στην εφαρμογή, πρέπει να ενημερώσουμε το DI container γι' αυτόν. Ο ευκολότερος τρόπος είναι να προετοιμάσουμε ένα factory που θα παράγει το αντικείμενο του router και να πούμε στη διαμόρφωση του container ότι πρέπει να το χρησιμοποιήσει. Ας υποθέσουμε ότι γι' αυτόν τον σκοπό γράφουμε τη μέθοδο App\Core\RouterFactory::createRouter():

namespace App\Core;

use Nette\Application\Routers\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute(/* ... */);
		return $router;
	}
}

Στη διαμόρφωση στη συνέχεια γράφουμε:

services:
	- App\Core\RouterFactory::createRouter

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

public static function createRouter(Nette\Database\Connection $db): RouteList
{
	// ...
}

SimpleRouter

Ένας πολύ απλούστερος router από τη συλλογή routes είναι ο SimpleRouter. Τον χρησιμοποιούμε όταν δεν έχουμε ιδιαίτερες απαιτήσεις για τη μορφή των URL, όταν δεν είναι διαθέσιμο το mod_rewrite (ή οι εναλλακτικές του) ή όταν δεν θέλουμε ακόμα να ασχοληθούμε με όμορφα URL.

Δημιουργεί διευθύνσεις περίπου σε αυτή τη μορφή:

http://example.com/?presenter=Product&action=detail&id=123

Η παράμετρος του κατασκευαστή του SimpleRouter είναι ο προεπιλεγμένος presenter & η action, στην οποία πρέπει να κατευθυνθεί, αν ανοίξουμε τη σελίδα χωρίς παραμέτρους, π.χ. http://example.com/.

// ο προεπιλεγμένος presenter θα είναι 'Home' και η action 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');

Συνιστούμε να ορίσετε τον SimpleRouter απευθείας στη διαμόρφωση:

services:
	- Nette\Application\Routers\SimpleRouter('Home:default')

SEO και κανονικοποίηση

Το framework συμβάλλει στο SEO (βελτιστοποίηση για μηχανές αναζήτησης) αποτρέποντας τη διπλή εμφάνιση περιεχομένου σε διαφορετικά URL. Αν υπάρχουν πολλαπλές διευθύνσεις που οδηγούν στον ίδιο στόχο, π.χ. /index και /index.html, το framework καθορίζει την πρώτη από αυτές ως την κύρια (κανονική) και ανακατευθύνει τις υπόλοιπες σε αυτήν χρησιμοποιώντας τον κωδικό HTTP 301. Χάρη σε αυτό, οι μηχανές αναζήτησης δεν ευρετηριάζουν τις σελίδες σας δύο φορές και δεν διασπούν το page rank τους.

Αυτή η διαδικασία ονομάζεται κανονικοποίηση. Η κανονική διεύθυνση URL είναι αυτή που δημιουργείται από τον router, δηλαδή η πρώτη κατάλληλη route στη συλλογή χωρίς τη σημαία OneWay. Γι' αυτό στη συλλογή αναφέρουμε τις κύριες routes πρώτες.

Η κανονικοποίηση εκτελείται από τον presenter, περισσότερα στο κεφάλαιο κανονικοποίηση.

HTTPS

Για να μπορούμε να χρησιμοποιούμε το πρωτόκολλο HTTPS, είναι απαραίτητο να το ενεργοποιήσουμε στο hosting και να διαμορφώσουμε σωστά τον διακομιστή.

Η ανακατεύθυνση ολόκληρου του ιστότοπου σε HTTPS πρέπει να ρυθμιστεί σε επίπεδο διακομιστή, για παράδειγμα, χρησιμοποιώντας το αρχείο .htaccess στον ριζικό κατάλογο της εφαρμογής μας, και αυτό με τον κωδικό HTTP 301. Η ρύθμιση μπορεί να διαφέρει ανάλογα με το hosting και μοιάζει περίπου έτσι:

<IfModule mod_rewrite.c>
	RewriteEngine On
	...
	RewriteCond %{HTTPS} off
	RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
	...
</IfModule>

Ο router δημιουργεί URL με το ίδιο πρωτόκολλο με το οποίο φορτώθηκε η σελίδα, οπότε τίποτα περισσότερο δεν χρειάζεται να ρυθμιστεί.

Αν όμως εξαιρετικά χρειαζόμαστε διαφορετικές routes να εκτελούνται με διαφορετικά πρωτόκολλα, το αναφέρουμε στη μάσκα της route:

// Θα δημιουργήσει διεύθυνση με HTTP
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// Θα δημιουργήσει διεύθυνση με HTTPS
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);

Αποσφαλμάτωση του router

Ο πίνακας δρομολόγησης που εμφανίζεται στη Tracy Bar είναι ένα χρήσιμο βοήθημα που εμφανίζει τη λίστα των routes και επίσης τις παραμέτρους που απέκτησε ο router από το URL.

Η πράσινη γραμμή με το σύμβολο ✓ αντιπροσωπεύει τη route που επεξεργάστηκε το τρέχον URL, με μπλε χρώμα και το σύμβολο ≈ επισημαίνονται οι routes που θα επεξεργάζονταν επίσης το URL αν η πράσινη δεν τις είχε προλάβει. Παρακάτω βλέπουμε τον τρέχοντα presenter & την action.

Ταυτόχρονα, αν συμβεί μια μη αναμενόμενη ανακατεύθυνση λόγω κανονικοποίησης, είναι χρήσιμο να κοιτάξετε τον πίνακα στη γραμμή redirect, όπου θα μάθετε πώς ο router κατανόησε αρχικά το URL και γιατί ανακατεύθυνε.

Κατά την αποσφαλμάτωση του router, συνιστούμε να ανοίξετε τα Developer Tools στο πρόγραμμα περιήγησης (Ctrl+Shift+I ή Cmd+Option+I) και στον πίνακα Network να απενεργοποιήσετε την cache, ώστε να μην αποθηκεύονται σε αυτήν οι ανακατευθύνσεις.

Απόδοση

Ο αριθμός των routes επηρεάζει την ταχύτητα του router. Ο αριθμός τους σίγουρα δεν θα πρέπει να υπερβαίνει μερικές δεκάδες. Αν ο ιστότοπός σας έχει πολύπλοκη δομή URL, μπορείτε να γράψετε έναν προσαρμοσμένο vlastní router.

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

routing:
	cache: true

Προσαρμοσμένος router

Οι παρακάτω γραμμές προορίζονται για πολύ προχωρημένους χρήστες. Μπορείτε να δημιουργήσετε τον δικό σας router και να τον ενσωματώσετε εντελώς φυσικά στη συλλογή των routes. Ο router είναι μια υλοποίηση του interface Nette\Routing\Router με δύο μεθόδους:

use Nette\Http\IRequest as HttpRequest;
use Nette\Http\UrlScript;

class MyRouter implements Nette\Routing\Router
{
	public function match(HttpRequest $httpRequest): ?array
	{
		// ...
	}

	public function constructUrl(array $params, UrlScript $refUrl): ?string
	{
		// ...
	}
}

Η μέθοδος match επεξεργάζεται το τρέχον αίτημα $httpRequest, από το οποίο μπορείτε να λάβετε όχι μόνο το URL, αλλά και τις κεφαλίδες κ.λπ., σε έναν πίνακα που περιέχει το όνομα του presenter και τις παραμέτρους του. Αν δεν μπορεί να επεξεργαστεί το αίτημα, επιστρέφει null. Κατά την επεξεργασία του αιτήματος, πρέπει να επιστρέψουμε τουλάχιστον τον presenter και την action. Το όνομα του presenter είναι πλήρες και περιέχει και τυχόν modules:

[
	'presenter' => 'Front:Home',
	'action' => 'default',
]

Η μέθοδος constructUrl αντίθετα συναρμολογεί από τον πίνακα παραμέτρων το τελικό απόλυτο URL. Γι' αυτό μπορεί να χρησιμοποιήσει πληροφορίες από την παράμετρο $refUrl, που είναι το τρέχον URL.

Τον προσθέτετε στη συλλογή των routes χρησιμοποιώντας το add():

$router = new Nette\Application\Routers\RouteList;
$router->add($myRouter);
$router->addRoute(/* ... */);
// ...

Αυτόνομη χρήση

Με την αυτόνομη χρήση εννοούμε τη χρήση των δυνατοτήτων του router σε μια εφαρμογή που δεν χρησιμοποιεί το Nette Application και τους presenters. Ισχύουν γι' αυτόν σχεδόν όλα όσα δείξαμε σε αυτό το κεφάλαιο, με τις εξής διαφορές:

Έτσι, ξανά δημιουργούμε μια μέθοδο που θα μας συναρμολογήσει τον router, π.χ.:

namespace App\Core;

use Nette\Routing\RouteList;

class RouterFactory
{
	public static function createRouter(): RouteList
	{
		$router = new RouteList;
		$router->addRoute('rss.xml', [
			'controller' => 'RssFeedController',
		]);
		$router->addRoute('article/<id \d+>', [
			'controller' => 'ArticleController',
		]);
		// ...
		return $router;
	}
}

Αν χρησιμοποιείτε DI container, το οποίο συνιστούμε, προσθέτουμε ξανά τη μέθοδο στη διαμόρφωση και στη συνέχεια λαμβάνουμε τον router μαζί με το αίτημα HTTP από το container:

$router = $container->getByType(Nette\Routing\Router::class);
$httpRequest = $container->getByType(Nette\Http\IRequest::class);

Ή δημιουργούμε τα αντικείμενα απευθείας:

$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();

Τώρα μένει μόνο να αφήσουμε τον router να δουλέψει:

$params = $router->match($httpRequest);
if ($params === null) {
	// δεν βρέθηκε αντίστοιχη route, αποστολή σφάλματος 404
	exit;
}

// επεξεργασία των ληφθέντων παραμέτρων
$controller = $params['controller'];
// ...

Και αντίστροφα, χρησιμοποιούμε τον router για να συναρμολογήσουμε έναν σύνδεσμο:

$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());
έκδοση: 4.0