Nette Documentation Preview

syntax
Útválasztás
***********

<div class=perex>

Az útválasztó felelős mindenért az URL-ekkel kapcsolatban, így nem kell többé gondolkodnod rajtuk. Megmutatjuk:

- hogyan állítsuk be a routert, hogy az URL-ek úgy nézzenek ki, ahogyan szeretnénk.
- néhány megjegyzést a SEO átirányításról
- és megmutatjuk, hogyan írhatod meg a saját routeredet.

</div>


Az emberibb URL-ek (vagy menőbb vagy csinosabb URL-ek) jobban használhatóak, emlékezetesebbek és pozitívan járulnak hozzá a SEO-hoz. A Nette ezt tartja szem előtt, és teljes mértékben megfelel a fejlesztők vágyainak. Pontosan úgy alakíthatja ki az alkalmazás URL-szerkezetét, ahogyan szeretné.
Akár az alkalmazás elkészülte után is megtervezheti, mivel ez kód- vagy sablonmódosítás nélkül is elvégezhető. Elegáns módon, [egyetlen helyen |#Integration], a routerben kerül meghatározásra, és nem szóródik szét megjegyzések formájában az összes bemutatóban.

A Nette router azért különleges, mert **kétirányú**, képes mind a HTTP-kérő URL-ek dekódolására, mind a linkek létrehozására. Tehát létfontosságú szerepet játszik a [Nette alkalmazásban |how-it-works#Nette Application], mert eldönti, hogy melyik prezenter és akció fogja végrehajtani az aktuális kérést, és a sablonban az [URL generálásához |creating-links] is használják stb.

A router azonban nem korlátozódik erre a felhasználásra, használhatja olyan alkalmazásokban is, ahol egyáltalán nem használnak prezentereket, REST API-khoz stb. Bővebben az [elkülönített használat |#separated usage] szakaszban.


Útvonalgyűjtés .[#toc-route-collection]
=======================================

Az URL címek definiálásának legkellemesebb módja az alkalmazásban a [api:Nette\Application\Routers\RouteList] osztályon keresztül történik. A definíció úgynevezett útvonalak listájából áll, azaz URL-címek maszkjaiból és a hozzájuk tartozó bemutatókból és műveletekből egy egyszerű API segítségével. Az útvonalakat nem kell megneveznünk.

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

A példa azt mondja, hogy ha megnyitjuk a `https://any-domain.com/rss.xml` prezenter a `rss` akcióval jelenik meg, ha a `https://domain.com/article/12` a `view` akcióval, stb. Ha nem találunk megfelelő útvonalat, a Nette Application egy [BadRequestException |api:Nette\Application\BadRequestException] kivétel dobásával válaszol, amely a felhasználó számára egy 404 Not Found hibaoldalként jelenik meg.


Az útvonalak sorrendje .[#toc-order-of-routes]
----------------------------------------------

Az útvonalak sorrendje **nagyon fontos**, mivel az útvonalakat a rendszer fentről lefelé haladva értékeli ki. A szabály az, hogy az útvonalakat **specifikusról az általánosra** jelentjük be:

```php
// ROSSZ: az 'rss.xml' az első útvonalra illeszkedik, és ezt félreérti <slug>-nak tekinti.
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

// JÓ
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');
```

Az útvonalakat a linkek generálásakor is felülről lefelé értékeljük ki:

```php
// ROSSZ: a 'Feed:rss' linket 'admin/feed/rss' néven generálja.
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

// JÓ
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
```

Nem tartjuk titokban Ön előtt, hogy a lista helyes felépítéséhez némi szakértelemre van szükség. Amíg bele nem jön, az [útvonalválasztó panel |#Debugging Router] hasznos eszköz lesz.


Maszk és paraméterek .[#toc-mask-and-parameters]
------------------------------------------------

A maszk a relatív elérési utat írja le a webhely gyökere alapján. A legegyszerűbb maszk egy statikus URL:

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

A maszkok gyakran tartalmaznak úgynevezett **paramétereket**. Ezeket szögletes zárójelek közé zárják (pl. `<year>`), és a célbemutatónak adják át, például a `renderShow(int $year)` metódusnak vagy a `$year` állandó paraméternek:

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

A példa azt mondja, hogy ha megnyitjuk a `https://any-domain.com/chronicle/2020` prezenter és a `show` művelet a `year: 2020` paraméterrel megjelenik.

A paraméterek alapértelmezett értékét közvetlenül a maszkban adhatjuk meg, és így opcionálissá válik:

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

Az útvonal mostantól elfogadja a `https://any-domain.com/chronicle/` címet fogja megjeleníteni a `year: 2020` paraméterrel.

Természetesen a bemutató és az akció neve is lehet paraméter. Például:

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

Ez az útvonal elfogad például egy URL-t a `/article/edit` illetve `/catalog/list` formában, és lefordítja azokat a `Article:edit` illetve `Catalog:list` előadókra és műveletekre.

A `presenter` és a `action` paramétereknek alapértelmezett értékeket ad:`Home` és `default`, ezért ezek opcionálisak. Az útvonal tehát elfogad egy `/article` URL-t is, és lefordítja `Article:default`-ként. Vagy fordítva, a `Product:default` hivatkozás a `/product` útvonalat generálja, az alapértelmezett `Home:default` hivatkozás a `/` útvonalat generálja.

A maszk nem csak a webhely gyökere alapján a relatív elérési utat írhatja le, hanem az abszolút elérési utat is, ha az kötőjellel kezdődik, vagy akár a teljes abszolút URL-t, ha két kötőjellel kezdődik:

```php
// relatív elérési út az alkalmazási dokumentum gyökeréhez
$router->addRoute('<presenter>/<action>', /* ... */);

// abszolút elérési út, relatív a szerver hostnevéhez
$router->addRoute('/<presenter>/<action>', /* ... */);

// abszolút URL, beleértve a hosztnevet is (de séma-relatív)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// abszolút URL, beleértve a sémát is
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);
```


Érvényesítési kifejezések .[#toc-validation-expressions]
--------------------------------------------------------

Minden paraméterhez megadható egy érvényesítési feltétel [szabályos kifejezéssel |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Például állítsuk be, hogy a `id` csak numerikus legyen, a `\d+` regexp használatával:

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

Az alapértelmezett reguláris kifejezés minden paraméterhez a következő `[^/]+`, azaz minden, kivéve a slash-t. Ha egy paraméternek a slash-re is illeszkednie kell, akkor a reguláris kifejezést a `.+`-ra állítjuk.

```php
// elfogadja a https://example.com/a/b/c címet, az elérési útvonal 'a/b/c'.
$router->addRoute('<path .+>', /* ... */);
```


Választható szekvenciák .[#toc-optional-sequences]
--------------------------------------------------

A szögletes zárójelek a maszk opcionális részeit jelölik. A maszk bármelyik része opcionális lehet, beleértve a paramétereket tartalmazó részeket is:

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

// Elfogadott URL-címek:      Paraméterek:
//   /en/download        lang => en, name => download
//   /download           lang => null, name => download
```

Természetesen, ha egy paraméter egy opcionális sorozat része, akkor az is opcionálissá válik. Ha nincs alapértelmezett értéke, akkor null lesz.

Az opcionális szakaszok a tartományban is lehetnek:

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

A szekvenciák szabadon egymásba ágyazhatók és kombinálhatók:

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

// Elfogadott URL-címek:
//   /en/hello
//   /en-us/hello
//   /hello
//   /hello/page-12
```

Az URL-generátor igyekszik az URL-t a lehető legrövidebbre fogalmazni, ezért ami elhagyható, azt elhagyja. Ezért például egy útvonal `index[.html]` generál egy `/index` elérési utat. Ezt a viselkedést megfordíthatja, ha a bal oldali szögletes zárójel után felkiáltójelet ír:

```php
// elfogadja a /hello és a /hello.html fájlt is, és /hello.html-t generál.
$router->addRoute('<name>[.html]', /* ... */);

// elfogadja a /hello és a /hello.html címeket is, generálja a /hello.html címet.
$router->addRoute('<name>[!.html]', /* ... */);
```

A szögletes zárójel nélküli opcionális paraméterek (azaz az alapértelmezett értékkel rendelkező paraméterek) úgy viselkednek, mintha így lennének becsomagolva:

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

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

Ha meg akarja változtatni a jobb oldali slash generálásának módját, azaz a `/home/` helyett egy `/home` kapjon, állítsa be az útvonalat így:

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


. .[#toc-wildcards]
-------------------

Az abszolút elérési útvonal maszkjában használhatjuk a következő jokereket, hogy elkerüljük például a tartomány beírását a maszkba, ami a fejlesztői és a termelési környezetben eltérő lehet:

- `%tld%` = legfelső szintű tartomány, pl. `com` vagy `org`
- `%sld%` = második szintű tartomány, pl. `example`
- `%domain%` = aldomainek nélküli tartomány, pl. `example.com`
- `%host%` = teljes hoszt, pl. `www.example.com`
- `%basePath%` = a gyökérkönyvtár elérési útvonala

```php
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action', /* ... */);
```


Speciális jelölés .[#toc-advanced-notation]
-------------------------------------------

Az útvonal célját, amelyet általában a `Presenter:action` formában írunk le, egy olyan tömb segítségével is ki lehet fejezni, amely meghatározza az egyes paramétereket és azok alapértelmezett értékeit:

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

Részletesebb specifikációhoz egy még bővebb formát lehet használni, ahol az alapértelmezett értékek mellett más paramétertulajdonságok is megadhatók, például egy érvényesítési reguláris kifejezés (lásd a `id` paramétert):

```php
use Nette\Routing\Route;

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

Fontos megjegyezni, hogy ha a tömbben definiált paraméterek nem szerepelnek az elérési útvonal maszkjában, akkor értékük nem módosítható, még az URL-ben kérdőjel után megadott lekérdezési paraméterekkel sem.


Szűrők és fordítások .[#toc-filters-and-translations]
-----------------------------------------------------

Jó gyakorlat, hogy a forráskódot angolul írjuk, de mi van akkor, ha a weboldalunknak le kell fordítani az URL-t más nyelvre? Egyszerű útvonalak, mint például:

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

angol nyelvű URL-eket generálnak, például `/product/123` vagy `/cart`. Ha azt szeretnénk, hogy az URL-ben szereplő bemutatókat és műveleteket németre fordítsák le (pl. `/produkt/123` vagy `/einkaufswagen`), használhatunk egy fordítószótárat. Ennek hozzáadásához már a második paraméter "beszédesebb" változatára van szükségünk:

```php
use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// string az URL-ben => prezenter
			'produkt' => 'Product',
			'einkaufswagen' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'liste' => 'list',
		],
	],
]);
```

Több szótárkulcsot is használhatunk ugyanahhoz a prezentálóhoz. Ezek különböző aliasokat hoznak létre hozzá. Az utolsó kulcsot tekintjük a kanonikus változatnak (vagyis annak, amelyik a generált URL-ben szerepel majd).

A fordítási táblázat így bármely paraméterre alkalmazható. Ha azonban a fordítás nem létezik, akkor az eredeti értéket veszi át. Ezt a viselkedést megváltoztathatjuk a `Route::FilterStrict => true` hozzáadásával, és az útvonal ekkor elutasítja az URL-t, ha az érték nem szerepel a szótárban.

A fordítási szótár mellett tömb formájában saját fordítási függvények beállítására is lehetőség van:

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

A `Route::FilterIn` függvény az URL-ben szereplő paraméter és a karakterlánc között konvertál, amelyet aztán átadunk a prezentálónak, a `FilterOut` függvény pedig az ellenkező irányú konvertálást biztosítja.

A `presenter`, `action` és `module` paraméterek már rendelkeznek előre definiált szűrőkkel, amelyek az URL-ben használt PascalCase, illetve camelCase stílus és a kebab-case között konvertálnak. A paraméterek alapértelmezett értékét már az átalakított formában írjuk ki, így például egy bemutató esetében azt írjuk, hogy `<presenter=ProductEdit>` helyett `<presenter=product-edit>`.


Általános szűrők .[#toc-general-filters]
----------------------------------------

A specifikus paraméterekhez tartozó szűrők mellett általános szűrőket is definiálhat, amelyek megkapják az összes paraméter asszociatív tömbjét, amelyeket bármilyen módon módosíthatnak, majd visszaadnak. Az általános szűrők a `null` kulcs alatt definiálhatók.

```php
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 { /* ... */ },
	],
]);
```

Az általános szűrők lehetővé teszik, hogy az útvonal viselkedését teljesen tetszőleges módon módosítsa. Használhatjuk őket például arra, hogy a paramétereket más paraméterek alapján módosítsuk. Például fordítás `<presenter>` és a `<action>` paraméter aktuális értéke alapján `<lang>`.

Ha egy paraméterhez egyéni szűrő van definiálva és egyidejűleg létezik egy általános szűrő, az egyéni `FilterIn` az általános előtt kerül végrehajtásra, és fordítva, az általános `FilterOut` az egyéni előtt kerül végrehajtásra. Így az általános szűrőn belül a `presenter` illetve a `action` paraméterek értékei PascalCase illetve camelCase stílusban íródnak.


Egyirányú zászló .[#toc-oneway-flag]
------------------------------------

Az egyirányú útvonalakat arra használják, hogy megőrizzék a régi URL-ek funkcionalitását, amelyeket az alkalmazás már nem generál, de még elfogad. Ezeket a `OneWay` jelzéssel jelöljük:

```php
// régi URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY);
// új URL /termék/123
$router->addRoute('product/<id>', 'Product:detail');
```

A régi URL elérésekor a bemutató automatikusan átirányítja az új URL-re, hogy a keresőmotorok ne indexeljék kétszer ezeket az oldalakat (lásd [SEO és kanonizáció |#SEO and canonization]).


Dinamikus útválasztás visszahívásokkal .[#toc-dynamic-routing-with-callbacks]
-----------------------------------------------------------------------------

A dinamikus útválasztás visszahívásokkal lehetővé teszi, hogy az útvonalakhoz közvetlenül hozzárendeljen függvényeket (visszahívásokat), amelyek a megadott útvonal meglátogatásakor végrehajtódnak. Ez a rugalmas funkció lehetővé teszi, hogy gyorsan és hatékonyan hozzon létre különböző végpontokat az alkalmazásához:

```php
$router->addRoute('test', function () {
	echo 'You are at the /test address';
});
```

A maszkban paramétereket is meghatározhat, amelyek automatikusan átadásra kerülnek a visszahívásnak:

```php
$router->addRoute('<lang cs|en>', function (string $lang) {
	echo match ($lang) {
		'cs' => 'Welcome to the Czech version of our website!',
		'en' => 'Welcome to the English version of our website!',
	};
});
```


Modulok .[#toc-modules]
-----------------------

Ha több olyan útvonalunk van, amelyek egy [modulhoz |modules] tartoznak, akkor a `withModule()` segítségével csoportosíthatjuk őket:

```php
$router = new RouteList;
$router->withModule('Forum') // a következő útválasztók a Forum modul részei
	->addRoute('rss', 'Feed:rss') // a bemutató a Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // a következő útválasztók a Forum:Admin modul részei
		->addRoute('sign:in', 'Bejelentkezés:in');
```

Alternatív megoldás a `module` paraméter használata:

```php
// Az URL manage/dashboard/default a prezenterhez tartozik Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);
```


Aldomainek .[#toc-subdomains]
-----------------------------

Az útvonalgyűjteményeket aldomainek szerint lehet csoportosítani:

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

A domainnévben használhat [vadjelzőket |#wildcards] is:

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


Path Prefix .[#toc-path-prefix]
-------------------------------

Az útvonalgyűjteményeket az URL-ben szereplő elérési útvonal szerint lehet csoportosítani:

```php
$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // megfelel az URL-nek /eshop/rss
	->addRoute('<presenter>/<action>'); // megfelel az URL-nek /eshop/<presenter>/<action>
```


Kombinációk .[#toc-combinations]
--------------------------------

A fenti felhasználás kombinálható:

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


A lekérdezési paraméterek .[#toc-query-parameters]
--------------------------------------------------

A maszkok tartalmazhatnak lekérdezési paramétereket is (az URL-ben a kérdőjel utáni paraméterek). Ezek nem határozhatnak meg érvényesítési kifejezést, de megváltoztathatják azt a nevet, amely alatt a bemutatónak átadják őket:

```php
// a 'cat' lekérdezési paramétert 'categoryId'-ként használja az alkalmazásban
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);
```


Foo paraméterek .[#toc-foo-parameters]
--------------------------------------

Most mélyebbre megyünk. A Foo paraméterek alapvetően névtelen paraméterek, amelyek lehetővé teszik a reguláris kifejezésekkel való egyezést. A következő útvonal megfelel a `/index`, `/index.html`, `/index.htm` és `/index.php`:

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

Lehetőség van arra is, hogy explicit módon definiáljunk egy karakterláncot, amelyet az URL generálásához használunk. A karakterláncot közvetlenül a kérdőjel után kell elhelyezni. A következő útvonal hasonló az előzőhöz, de a `/index` helyett a `/index.html` címet generálja, mivel a `.html` karakterláncot "generált értékként" állítja be.

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


Integráció .[#toc-integration]
==============================

Ahhoz, hogy a routerünket az alkalmazásba kapcsoljuk, tájékoztatnunk kell róla a DI konténert. A legegyszerűbb, ha elkészítjük a gyárat, amely a router objektumot fogja felépíteni, és megmondjuk a konténer konfigurációjának, hogy használja azt. Tegyük fel, hogy írunk egy metódust erre a célra `App\Core\RouterFactory::createRouter()`:

```php
namespace App\Core;

use Nette\Application\Routers\RouteList;

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

Ezután beírjuk a [konfigurációba |dependency-injection:services]:

```neon
services:
	- App\Core\RouterFactory::createRouter
```

Az esetleges függőségeket, például az adatbázis-kapcsolatot stb., az [autowiring |dependency-injection:autowiring] segítségével paraméterként átadjuk a gyári metódusnak:

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


SimpleRouter .[#toc-simplerouter]
=================================

A Route Collectionnél sokkal egyszerűbb útválasztó a [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Ezt akkor használhatjuk, ha nincs szükség egy adott URL formátumra, ha a `mod_rewrite` (vagy alternatívák) nem állnak rendelkezésre, vagy ha egyszerűen csak nem akarunk még a felhasználóbarát URL-ekkel bajlódni.

Nagyjából ilyen formában generál címeket:

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

A `SimpleRouter` konstruktor paramétere egy alapértelmezett bemutató és művelet, azaz a végrehajtandó művelet, ha további paraméterek nélkül megnyitjuk pl. a `http://example.com/` címet.

```php
// alapértelmezett bemutató 'Home' és művelet 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');
```

Javasoljuk, hogy a SimpleRouter-t közvetlenül a [konfigurációban |dependency-injection:services] definiáljuk:

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


SEO és kanonizáció .[#toc-seo-and-canonization]
===============================================

A keretrendszer növeli a SEO-t (keresőmotor-optimalizálás) azáltal, hogy megakadályozza a tartalom duplikálódását a különböző URL-címeken. Ha több cím hivatkozik ugyanarra a célpontra, pl. `/index` és `/index.html`, a keretrendszer az elsőt határozza meg elsődlegesnek (kanonikusnak), és a többit 301-es HTTP-kóddal átirányítja rá. Ennek köszönhetően a keresőmotorok nem indexelik kétszer az oldalakat, és nem rontják az oldalrangsorukat. .

Ezt a folyamatot nevezzük kanonizációnak. A kanonikus URL az, amelyet az útválasztó generál, azaz a [gyűjteményben |#route-collection] az első megfelelő útvonal a OneWay jelző nélkül. Ezért a gyűjteményben először **elsődleges útvonalakat** sorolunk fel.

A kanonizálást a bemutató végzi, bővebben a [kanonizálás |presenters#Canonization] fejezetben.


HTTPS .[#toc-https]
===================

A HTTPS protokoll használatához aktiválni kell azt a tárhelyen, és konfigurálni kell a szervert.

A teljes webhely átirányítását HTTPS-re szerverszinten kell elvégezni, például az alkalmazásunk gyökérkönyvtárában lévő .htaccess fájl segítségével, 301-es HTTP-kóddal. A beállítások a tárhelytől függően eltérőek lehetnek, és valahogy így néz ki:

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

Az útválasztó ugyanolyan protokollal generál egy URL-t, mint amilyen protokollal az oldal betöltődött, így semmi mást nem kell beállítani.

Ha azonban kivételesen szükségünk van arra, hogy különböző protokollok alatt különböző útvonalakat futtassunk, akkor ezt az útvonalmaszkban fogjuk megadni:

```php
// HTTP-címet generál
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// HTTPS-címet generál
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);
```


Debugging Router .[#toc-debugging-router]
=========================================

A [Tracy Barban |tracy:] megjelenő útválasztó sáv egy hasznos eszköz, amely megjeleníti az útvonalak listáját, valamint az útválasztó által az URL-ből kapott paramétereket.

A ✓ szimbólummal ellátott zöld sáv az aktuális URL-nek megfelelő útvonalat jelzi, a ≈ szimbólummal ellátott kék sávok pedig azokat az útvonalakat jelzik, amelyek szintén megfelelnének az URL-nek, ha a zöld nem előzné meg őket. Továbbra is látjuk az aktuális előadót & akciót.

[* routing-debugger.webp *]

Ugyanakkor, ha a [kanonikalizálás |#SEO and Canonization] miatt váratlan átirányítás történik, hasznos megnézni az *átirányítás* sávot, hogy lássuk, hogyan értette eredetileg az útválasztó az URL-t, és miért irányított át.

.[note]
Az útválasztó hibakeresésekor ajánlott megnyitni a böngészőben a Fejlesztői eszközök eszközeit (Ctrl+Shift+I vagy Cmd+Option+I), és letiltani a gyorsítótárat a Hálózat panelen, hogy az átirányítások ne tárolódjanak benne.


Teljesítmény .[#toc-performance]
================================

Az útvonalak száma befolyásolja az útválasztó sebességét. Számuk semmiképpen sem haladhatja meg a néhány tucatot. Ha webhelye túlságosan bonyolult URL-struktúrával rendelkezik, írhat egy [egyéni útválasztót |#custom router].

Ha az útválasztónak nincsenek függőségei, például egy adatbázistól, és a gyárának nincsenek argumentumai, akkor a lefordított formáját közvetlenül egy DI konténerbe szerializálhatjuk, és így az alkalmazást valamivel gyorsabbá tehetjük.

```neon
routing:
	cache: true
```


Egyéni útválasztó .[#toc-custom-router]
=======================================

A következő sorok nagyon haladó felhasználóknak szólnak. Létrehozhatja saját útválasztóját, és természetesen hozzáadhatja az útvonalgyűjteményéhez. Az útválasztó a [api:Nette\Routing\Router] interfész implementációja két metódussal:

```php
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
	{
		// ...
	}
}
```

A `match` metódus az aktuális [$httpRequest-et |http:request], amelyből nem csak az URL, hanem a fejlécek stb. is kinyerhetők, egy tömbben dolgozza fel, amely a bemutató nevét és paramétereit tartalmazza. Ha nem tudja feldolgozni a kérést, akkor null értéket ad vissza.
A kérés feldolgozásakor legalább a prezentert és az actiont kell visszaadnunk. A prezenter neve teljes, és tartalmazza az esetleges modulokat:

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

A `constructUrl` módszer ezzel szemben abszolút URL-t generál a paraméterek tömbjéből. Használhatja a `$refUrl` paraméterből származó információt, amely az aktuális URL.

Egyéni útvonal hozzáadásához az útvonalkollekcióhoz a `add()` használatával:

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


Elkülönített használat .[#toc-separated-usage]
==============================================

A szeparált használat alatt a router képességeinek olyan alkalmazásban történő használatát értjük, amely nem használja a Nette alkalmazást és a prezentereket. Szinte minden, amit ebben a fejezetben bemutattunk, vonatkozik rá, a következő különbségekkel:

- az útvonalgyűjteményekhez az osztályt használjuk [api:Nette\Routing\RouteList]
- egyszerű útválasztó osztályként [api:Nette\Routing\SimpleRouter]
- mivel nincs `Presenter:action` pár, a [Advanced jelölést |#Advanced notation]használjuk.

Tehát ismét létrehozunk egy metódust, amely létrehoz egy útválasztót, például:

```php
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;
	}
}
```

Ha DI konténert használsz, amit ajánlunk, add hozzá a metódust ismét a konfigurációhoz, majd a HTTP-kérelemmel együtt kapd meg a routert a konténerből:

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

Vagy közvetlenül hozzuk létre az objektumokat:

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

Most hagynunk kell, hogy a router működjön:

```php
$params = $router->match($httpRequest);
if ($params === null) {
	// nem találtunk megfelelő útvonalat, 404-es hibát küldünk.
	exit;
}

// feldolgozzuk a kapott paramétereket
$controller = $params['controller'];
// ...
```

És fordítva, az útválasztót fogjuk használni a kapcsolat létrehozására:

```php
$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());
```


{{composer: nette/router}}

Útválasztás

Az útválasztó felelős mindenért az URL-ekkel kapcsolatban, így nem kell többé gondolkodnod rajtuk. Megmutatjuk:

  • hogyan állítsuk be a routert, hogy az URL-ek úgy nézzenek ki, ahogyan szeretnénk.
  • néhány megjegyzést a SEO átirányításról
  • és megmutatjuk, hogyan írhatod meg a saját routeredet.

Az emberibb URL-ek (vagy menőbb vagy csinosabb URL-ek) jobban használhatóak, emlékezetesebbek és pozitívan járulnak hozzá a SEO-hoz. A Nette ezt tartja szem előtt, és teljes mértékben megfelel a fejlesztők vágyainak. Pontosan úgy alakíthatja ki az alkalmazás URL-szerkezetét, ahogyan szeretné. Akár az alkalmazás elkészülte után is megtervezheti, mivel ez kód- vagy sablonmódosítás nélkül is elvégezhető. Elegáns módon, egyetlen helyen, a routerben kerül meghatározásra, és nem szóródik szét megjegyzések formájában az összes bemutatóban.

A Nette router azért különleges, mert kétirányú, képes mind a HTTP-kérő URL-ek dekódolására, mind a linkek létrehozására. Tehát létfontosságú szerepet játszik a Nette alkalmazásban, mert eldönti, hogy melyik prezenter és akció fogja végrehajtani az aktuális kérést, és a sablonban az URL generálásához is használják stb.

A router azonban nem korlátozódik erre a felhasználásra, használhatja olyan alkalmazásokban is, ahol egyáltalán nem használnak prezentereket, REST API-khoz stb. Bővebben az elkülönített használat szakaszban.

Útvonalgyűjtés

Az URL címek definiálásának legkellemesebb módja az alkalmazásban a Nette\Application\Routers\RouteList osztályon keresztül történik. A definíció úgynevezett útvonalak listájából áll, azaz URL-címek maszkjaiból és a hozzájuk tartozó bemutatókból és műveletekből egy egyszerű API segítségével. Az útvonalakat nem kell megneveznünk.

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

A példa azt mondja, hogy ha megnyitjuk a https://any-domain.com/rss.xml prezenter a rss akcióval jelenik meg, ha a https://domain.com/article/12 a view akcióval, stb. Ha nem találunk megfelelő útvonalat, a Nette Application egy BadRequestException kivétel dobásával válaszol, amely a felhasználó számára egy 404 Not Found hibaoldalként jelenik meg.

Az útvonalak sorrendje

Az útvonalak sorrendje nagyon fontos, mivel az útvonalakat a rendszer fentről lefelé haladva értékeli ki. A szabály az, hogy az útvonalakat specifikusról az általánosra jelentjük be:

// ROSSZ: az 'rss.xml' az első útvonalra illeszkedik, és ezt félreérti <slug>-nak tekinti.
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

// JÓ
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');

Az útvonalakat a linkek generálásakor is felülről lefelé értékeljük ki:

// ROSSZ: a 'Feed:rss' linket 'admin/feed/rss' néven generálja.
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');
$router->addRoute('rss.xml', 'Feed:rss');

// JÓ
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('admin/<presenter>/<action>', 'Admin:default');

Nem tartjuk titokban Ön előtt, hogy a lista helyes felépítéséhez némi szakértelemre van szükség. Amíg bele nem jön, az útvonalválasztó panel hasznos eszköz lesz.

Maszk és paraméterek

A maszk a relatív elérési utat írja le a webhely gyökere alapján. A legegyszerűbb maszk egy statikus URL:

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

A maszkok gyakran tartalmaznak úgynevezett paramétereket. Ezeket szögletes zárójelek közé zárják (pl. <year>), és a célbemutatónak adják át, például a renderShow(int $year) metódusnak vagy a $year állandó paraméternek:

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

A példa azt mondja, hogy ha megnyitjuk a https://any-domain.com/chronicle/2020 prezenter és a show művelet a year: 2020 paraméterrel megjelenik.

A paraméterek alapértelmezett értékét közvetlenül a maszkban adhatjuk meg, és így opcionálissá válik:

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

Az útvonal mostantól elfogadja a https://any-domain.com/chronicle/ címet fogja megjeleníteni a year: 2020 paraméterrel.

Természetesen a bemutató és az akció neve is lehet paraméter. Például:

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

Ez az útvonal elfogad például egy URL-t a /article/edit illetve /catalog/list formában, és lefordítja azokat a Article:edit illetve Catalog:list előadókra és műveletekre.

A presenter és a action paramétereknek alapértelmezett értékeket ad:Home és default, ezért ezek opcionálisak. Az útvonal tehát elfogad egy /article URL-t is, és lefordítja Article:default-ként. Vagy fordítva, a Product:default hivatkozás a /product útvonalat generálja, az alapértelmezett Home:default hivatkozás a / útvonalat generálja.

A maszk nem csak a webhely gyökere alapján a relatív elérési utat írhatja le, hanem az abszolút elérési utat is, ha az kötőjellel kezdődik, vagy akár a teljes abszolút URL-t, ha két kötőjellel kezdődik:

// relatív elérési út az alkalmazási dokumentum gyökeréhez
$router->addRoute('<presenter>/<action>', /* ... */);

// abszolút elérési út, relatív a szerver hostnevéhez
$router->addRoute('/<presenter>/<action>', /* ... */);

// abszolút URL, beleértve a hosztnevet is (de séma-relatív)
$router->addRoute('//<lang>.example.com/<presenter>/<action>', /* ... */);

// abszolút URL, beleértve a sémát is
$router->addRoute('https://<lang>.example.com/<presenter>/<action>', /* ... */);

Érvényesítési kifejezések

Minden paraméterhez megadható egy érvényesítési feltétel szabályos kifejezéssel. Például állítsuk be, hogy a id csak numerikus legyen, a \d+ regexp használatával:

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

Az alapértelmezett reguláris kifejezés minden paraméterhez a következő [^/]+, azaz minden, kivéve a slash-t. Ha egy paraméternek a slash-re is illeszkednie kell, akkor a reguláris kifejezést a .+-ra állítjuk.

// elfogadja a https://example.com/a/b/c címet, az elérési útvonal 'a/b/c'.
$router->addRoute('<path .+>', /* ... */);

Választható szekvenciák

A szögletes zárójelek a maszk opcionális részeit jelölik. A maszk bármelyik része opcionális lehet, beleértve a paramétereket tartalmazó részeket is:

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

// Elfogadott URL-címek:      Paraméterek:
//   /en/download        lang => en, name => download
//   /download           lang => null, name => download

Természetesen, ha egy paraméter egy opcionális sorozat része, akkor az is opcionálissá válik. Ha nincs alapértelmezett értéke, akkor null lesz.

Az opcionális szakaszok a tartományban is lehetnek:

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

A szekvenciák szabadon egymásba ágyazhatók és kombinálhatók:

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

// Elfogadott URL-címek:
//   /en/hello
//   /en-us/hello
//   /hello
//   /hello/page-12

Az URL-generátor igyekszik az URL-t a lehető legrövidebbre fogalmazni, ezért ami elhagyható, azt elhagyja. Ezért például egy útvonal index[.html] generál egy /index elérési utat. Ezt a viselkedést megfordíthatja, ha a bal oldali szögletes zárójel után felkiáltójelet ír:

// elfogadja a /hello és a /hello.html fájlt is, és /hello.html-t generál.
$router->addRoute('<name>[.html]', /* ... */);

// elfogadja a /hello és a /hello.html címeket is, generálja a /hello.html címet.
$router->addRoute('<name>[!.html]', /* ... */);

A szögletes zárójel nélküli opcionális paraméterek (azaz az alapértelmezett értékkel rendelkező paraméterek) úgy viselkednek, mintha így lennének becsomagolva:

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

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

Ha meg akarja változtatni a jobb oldali slash generálásának módját, azaz a /home/ helyett egy /home kapjon, állítsa be az útvonalat így:

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

.

Az abszolút elérési útvonal maszkjában használhatjuk a következő jokereket, hogy elkerüljük például a tartomány beírását a maszkba, ami a fejlesztői és a termelési környezetben eltérő lehet:

  • %tld% = legfelső szintű tartomány, pl. com vagy org
  • %sld% = második szintű tartomány, pl. example
  • %domain% = aldomainek nélküli tartomány, pl. example.com
  • %host% = teljes hoszt, pl. www.example.com
  • %basePath% = a gyökérkönyvtár elérési útvonala
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action', /* ... */);

Speciális jelölés

Az útvonal célját, amelyet általában a Presenter:action formában írunk le, egy olyan tömb segítségével is ki lehet fejezni, amely meghatározza az egyes paramétereket és azok alapértelmezett értékeit:

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

Részletesebb specifikációhoz egy még bővebb formát lehet használni, ahol az alapértelmezett értékek mellett más paramétertulajdonságok is megadhatók, például egy érvényesítési reguláris kifejezés (lásd a id paramétert):

use Nette\Routing\Route;

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

Fontos megjegyezni, hogy ha a tömbben definiált paraméterek nem szerepelnek az elérési útvonal maszkjában, akkor értékük nem módosítható, még az URL-ben kérdőjel után megadott lekérdezési paraméterekkel sem.

Szűrők és fordítások

Jó gyakorlat, hogy a forráskódot angolul írjuk, de mi van akkor, ha a weboldalunknak le kell fordítani az URL-t más nyelvre? Egyszerű útvonalak, mint például:

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

angol nyelvű URL-eket generálnak, például /product/123 vagy /cart. Ha azt szeretnénk, hogy az URL-ben szereplő bemutatókat és műveleteket németre fordítsák le (pl. /produkt/123 vagy /einkaufswagen), használhatunk egy fordítószótárat. Ennek hozzáadásához már a második paraméter „beszédesebb“ változatára van szükségünk:

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// string az URL-ben => prezenter
			'produkt' => 'Product',
			'einkaufswagen' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'liste' => 'list',
		],
	],
]);

Több szótárkulcsot is használhatunk ugyanahhoz a prezentálóhoz. Ezek különböző aliasokat hoznak létre hozzá. Az utolsó kulcsot tekintjük a kanonikus változatnak (vagyis annak, amelyik a generált URL-ben szerepel majd).

A fordítási táblázat így bármely paraméterre alkalmazható. Ha azonban a fordítás nem létezik, akkor az eredeti értéket veszi át. Ezt a viselkedést megváltoztathatjuk a Route::FilterStrict => true hozzáadásával, és az útvonal ekkor elutasítja az URL-t, ha az érték nem szerepel a szótárban.

A fordítási szótár mellett tömb formájában saját fordítási függvények beállítására is lehetőség van:

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

A Route::FilterIn függvény az URL-ben szereplő paraméter és a karakterlánc között konvertál, amelyet aztán átadunk a prezentálónak, a FilterOut függvény pedig az ellenkező irányú konvertálást biztosítja.

A presenter, action és module paraméterek már rendelkeznek előre definiált szűrőkkel, amelyek az URL-ben használt PascalCase, illetve camelCase stílus és a kebab-case között konvertálnak. A paraméterek alapértelmezett értékét már az átalakított formában írjuk ki, így például egy bemutató esetében azt írjuk, hogy <presenter=ProductEdit> helyett <presenter=product-edit>.

Általános szűrők

A specifikus paraméterekhez tartozó szűrők mellett általános szűrőket is definiálhat, amelyek megkapják az összes paraméter asszociatív tömbjét, amelyeket bármilyen módon módosíthatnak, majd visszaadnak. Az általános szűrők a null kulcs alatt definiálhatók.

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 { /* ... */ },
	],
]);

Az általános szűrők lehetővé teszik, hogy az útvonal viselkedését teljesen tetszőleges módon módosítsa. Használhatjuk őket például arra, hogy a paramétereket más paraméterek alapján módosítsuk. Például fordítás <presenter> és a <action> paraméter aktuális értéke alapján <lang>.

Ha egy paraméterhez egyéni szűrő van definiálva és egyidejűleg létezik egy általános szűrő, az egyéni FilterIn az általános előtt kerül végrehajtásra, és fordítva, az általános FilterOut az egyéni előtt kerül végrehajtásra. Így az általános szűrőn belül a presenter illetve a action paraméterek értékei PascalCase illetve camelCase stílusban íródnak.

Egyirányú zászló

Az egyirányú útvonalakat arra használják, hogy megőrizzék a régi URL-ek funkcionalitását, amelyeket az alkalmazás már nem generál, de még elfogad. Ezeket a OneWay jelzéssel jelöljük:

// régi URL /product-info?id=123
$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY);
// új URL /termék/123
$router->addRoute('product/<id>', 'Product:detail');

A régi URL elérésekor a bemutató automatikusan átirányítja az új URL-re, hogy a keresőmotorok ne indexeljék kétszer ezeket az oldalakat (lásd SEO és kanonizáció).

Dinamikus útválasztás visszahívásokkal

A dinamikus útválasztás visszahívásokkal lehetővé teszi, hogy az útvonalakhoz közvetlenül hozzárendeljen függvényeket (visszahívásokat), amelyek a megadott útvonal meglátogatásakor végrehajtódnak. Ez a rugalmas funkció lehetővé teszi, hogy gyorsan és hatékonyan hozzon létre különböző végpontokat az alkalmazásához:

$router->addRoute('test', function () {
	echo 'You are at the /test address';
});

A maszkban paramétereket is meghatározhat, amelyek automatikusan átadásra kerülnek a visszahívásnak:

$router->addRoute('<lang cs|en>', function (string $lang) {
	echo match ($lang) {
		'cs' => 'Welcome to the Czech version of our website!',
		'en' => 'Welcome to the English version of our website!',
	};
});

Modulok

Ha több olyan útvonalunk van, amelyek egy modulhoz tartoznak, akkor a withModule() segítségével csoportosíthatjuk őket:

$router = new RouteList;
$router->withModule('Forum') // a következő útválasztók a Forum modul részei
	->addRoute('rss', 'Feed:rss') // a bemutató a Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // a következő útválasztók a Forum:Admin modul részei
		->addRoute('sign:in', 'Bejelentkezés:in');

Alternatív megoldás a module paraméter használata:

// Az URL manage/dashboard/default a prezenterhez tartozik Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);

Aldomainek

Az útvonalgyűjteményeket aldomainek szerint lehet csoportosítani:

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

A domainnévben használhat vadjelzőket is:

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

Path Prefix

Az útvonalgyűjteményeket az URL-ben szereplő elérési útvonal szerint lehet csoportosítani:

$router = new RouteList;
$router->withPath('eshop')
	->addRoute('rss', 'Feed:rss') // megfelel az URL-nek /eshop/rss
	->addRoute('<presenter>/<action>'); // megfelel az URL-nek /eshop/<presenter>/<action>

Kombinációk

A fenti felhasználás kombinálható:

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

A lekérdezési paraméterek

A maszkok tartalmazhatnak lekérdezési paramétereket is (az URL-ben a kérdőjel utáni paraméterek). Ezek nem határozhatnak meg érvényesítési kifejezést, de megváltoztathatják azt a nevet, amely alatt a bemutatónak átadják őket:

// a 'cat' lekérdezési paramétert 'categoryId'-ként használja az alkalmazásban
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);

Foo paraméterek

Most mélyebbre megyünk. A Foo paraméterek alapvetően névtelen paraméterek, amelyek lehetővé teszik a reguláris kifejezésekkel való egyezést. A következő útvonal megfelel a /index, /index.html, /index.htm és /index.php:

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

Lehetőség van arra is, hogy explicit módon definiáljunk egy karakterláncot, amelyet az URL generálásához használunk. A karakterláncot közvetlenül a kérdőjel után kell elhelyezni. A következő útvonal hasonló az előzőhöz, de a /index helyett a /index.html címet generálja, mivel a .html karakterláncot „generált értékként“ állítja be.

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

Integráció

Ahhoz, hogy a routerünket az alkalmazásba kapcsoljuk, tájékoztatnunk kell róla a DI konténert. A legegyszerűbb, ha elkészítjük a gyárat, amely a router objektumot fogja felépíteni, és megmondjuk a konténer konfigurációjának, hogy használja azt. Tegyük fel, hogy írunk egy metódust erre a célra 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;
	}
}

Ezután beírjuk a konfigurációba:

services:
	- App\Core\RouterFactory::createRouter

Az esetleges függőségeket, például az adatbázis-kapcsolatot stb., az autowiring segítségével paraméterként átadjuk a gyári metódusnak:

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

SimpleRouter

A Route Collectionnél sokkal egyszerűbb útválasztó a SimpleRouter. Ezt akkor használhatjuk, ha nincs szükség egy adott URL formátumra, ha a mod_rewrite (vagy alternatívák) nem állnak rendelkezésre, vagy ha egyszerűen csak nem akarunk még a felhasználóbarát URL-ekkel bajlódni.

Nagyjából ilyen formában generál címeket:

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

A SimpleRouter konstruktor paramétere egy alapértelmezett bemutató és művelet, azaz a végrehajtandó művelet, ha további paraméterek nélkül megnyitjuk pl. a http://example.com/ címet.

// alapértelmezett bemutató 'Home' és művelet 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');

Javasoljuk, hogy a SimpleRouter-t közvetlenül a konfigurációban definiáljuk:

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

SEO és kanonizáció

A keretrendszer növeli a SEO-t (keresőmotor-optimalizálás) azáltal, hogy megakadályozza a tartalom duplikálódását a különböző URL-címeken. Ha több cím hivatkozik ugyanarra a célpontra, pl. /index és /index.html, a keretrendszer az elsőt határozza meg elsődlegesnek (kanonikusnak), és a többit 301-es HTTP-kóddal átirányítja rá. Ennek köszönhetően a keresőmotorok nem indexelik kétszer az oldalakat, és nem rontják az oldalrangsorukat. .

Ezt a folyamatot nevezzük kanonizációnak. A kanonikus URL az, amelyet az útválasztó generál, azaz a gyűjteményben az első megfelelő útvonal a OneWay jelző nélkül. Ezért a gyűjteményben először elsődleges útvonalakat sorolunk fel.

A kanonizálást a bemutató végzi, bővebben a kanonizálás fejezetben.

HTTPS

A HTTPS protokoll használatához aktiválni kell azt a tárhelyen, és konfigurálni kell a szervert.

A teljes webhely átirányítását HTTPS-re szerverszinten kell elvégezni, például az alkalmazásunk gyökérkönyvtárában lévő .htaccess fájl segítségével, 301-es HTTP-kóddal. A beállítások a tárhelytől függően eltérőek lehetnek, és valahogy így néz ki:

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

Az útválasztó ugyanolyan protokollal generál egy URL-t, mint amilyen protokollal az oldal betöltődött, így semmi mást nem kell beállítani.

Ha azonban kivételesen szükségünk van arra, hogy különböző protokollok alatt különböző útvonalakat futtassunk, akkor ezt az útvonalmaszkban fogjuk megadni:

// HTTP-címet generál
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// HTTPS-címet generál
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);

Debugging Router

Tracy Barban megjelenő útválasztó sáv egy hasznos eszköz, amely megjeleníti az útvonalak listáját, valamint az útválasztó által az URL-ből kapott paramétereket.

A ✓ szimbólummal ellátott zöld sáv az aktuális URL-nek megfelelő útvonalat jelzi, a ≈ szimbólummal ellátott kék sávok pedig azokat az útvonalakat jelzik, amelyek szintén megfelelnének az URL-nek, ha a zöld nem előzné meg őket. Továbbra is látjuk az aktuális előadót & akciót.

Ugyanakkor, ha a kanonikalizálás miatt váratlan átirányítás történik, hasznos megnézni az átirányítás sávot, hogy lássuk, hogyan értette eredetileg az útválasztó az URL-t, és miért irányított át.

Az útválasztó hibakeresésekor ajánlott megnyitni a böngészőben a Fejlesztői eszközök eszközeit (Ctrl+Shift+I vagy Cmd+Option+I), és letiltani a gyorsítótárat a Hálózat panelen, hogy az átirányítások ne tárolódjanak benne.

Teljesítmény

Az útvonalak száma befolyásolja az útválasztó sebességét. Számuk semmiképpen sem haladhatja meg a néhány tucatot. Ha webhelye túlságosan bonyolult URL-struktúrával rendelkezik, írhat egy egyéni útválasztót.

Ha az útválasztónak nincsenek függőségei, például egy adatbázistól, és a gyárának nincsenek argumentumai, akkor a lefordított formáját közvetlenül egy DI konténerbe szerializálhatjuk, és így az alkalmazást valamivel gyorsabbá tehetjük.

routing:
	cache: true

Egyéni útválasztó

A következő sorok nagyon haladó felhasználóknak szólnak. Létrehozhatja saját útválasztóját, és természetesen hozzáadhatja az útvonalgyűjteményéhez. Az útválasztó a Nette\Routing\Router interfész implementációja két metódussal:

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
	{
		// ...
	}
}

A match metódus az aktuális $httpRequest-et, amelyből nem csak az URL, hanem a fejlécek stb. is kinyerhetők, egy tömbben dolgozza fel, amely a bemutató nevét és paramétereit tartalmazza. Ha nem tudja feldolgozni a kérést, akkor null értéket ad vissza. A kérés feldolgozásakor legalább a prezentert és az actiont kell visszaadnunk. A prezenter neve teljes, és tartalmazza az esetleges modulokat:

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

A constructUrl módszer ezzel szemben abszolút URL-t generál a paraméterek tömbjéből. Használhatja a $refUrl paraméterből származó információt, amely az aktuális URL.

Egyéni útvonal hozzáadásához az útvonalkollekcióhoz a add() használatával:

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

Elkülönített használat

A szeparált használat alatt a router képességeinek olyan alkalmazásban történő használatát értjük, amely nem használja a Nette alkalmazást és a prezentereket. Szinte minden, amit ebben a fejezetben bemutattunk, vonatkozik rá, a következő különbségekkel:

Tehát ismét létrehozunk egy metódust, amely létrehoz egy útválasztót, például:

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

Ha DI konténert használsz, amit ajánlunk, add hozzá a metódust ismét a konfigurációhoz, majd a HTTP-kérelemmel együtt kapd meg a routert a konténerből:

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

Vagy közvetlenül hozzuk létre az objektumokat:

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

Most hagynunk kell, hogy a router működjön:

$params = $router->match($httpRequest);
if ($params === null) {
	// nem találtunk megfelelő útvonalat, 404-es hibát küldünk.
	exit;
}

// feldolgozzuk a kapott paramétereket
$controller = $params['controller'];
// ...

És fordítva, az útválasztót fogjuk használni a kapcsolat létrehozására:

$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());