Маршрутизація

Маршрутизатор подбає про все, що пов'язано з URL-адресами, так що вам більше не доведеться про них думати. Давайте покажемо:

  • як налаштувати маршрутизатор так, щоб URL-адреси були такими, якими ви хочете їх бачити
  • як налаштувати SEO та перенаправлення
  • як написати свій власний маршрутизатор

Більш людські URL (або круті або красиві URL) зручніші для використання, краще запам'ятовуються і позитивно впливають на SEO. Nette враховує це і повністю задовольняє бажання розробників. Ви можете розробити структуру URL для вашого застосунку саме так, як ви хочете. Ви можете навіть розробити її після того, як застосунок буде готовий, оскільки це можна зробити без будь-яких змін коду або шаблону. Вона визначається елегантним чином в одному єдиному місці, у маршрутизаторі, і не розкидана у вигляді анотацій по всіх презентаторах.

Маршрутизатор у Nette особливий, бо він двонаправлений, він може як декодувати URL HTTP-запитів, так і створювати посилання. Тому він відіграє важливу роль у Nette Application, оскільки він вирішує, який ведучий і дія виконуватимуть поточний запит, а також використовується для генерації URL у шаблоні тощо.

Однак маршрутизатор не обмежується цим застосуванням, ви можете використовувати його в додатках, де ведучі взагалі не використовуються, для REST API тощо. Докладніше в розділі роздільне використання.

Колекція маршрутів

Найбільш приємним способом визначення URL-адрес у додатку є клас Nette\Application\Routers\RouteList. Визначення складається зі списку так званих маршрутів, тобто масок URL-адрес і пов'язаних із ними презентерів і дій за допомогою простого API. Нам не потрібно називати маршрути.

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

У прикладі йдеться про те, що якщо ми відкриємо https://any-domain.com/rss.xml з дією rss і т. д. Якщо відповідний маршрут не знайдено, застосунок Nette відповідає винятком BadRequestException, який відображається для користувача як сторінка помилки 404 Not Found.

Порядок маршрутів

Порядок, у якому перелічено маршрути, дуже важливий, тому що вони оцінюються послідовно зверху вниз. Правило полягає в тому, що ми оголошуємо маршрути від конкретних до загальних:

// ПОМИЛКА: 'rss.xml' відповідає першому маршруту і неправильно сприймає його як <slug>.
$router->addRoute('<slug>', 'Article:view');
$router->addRoute('rss.xml', 'Feed:rss');

// ДЕТАЛЬНІШЕ
$router->addRoute('rss.xml', 'Feed:rss');
$router->addRoute('<slug>', 'Article:view');

Маршрути також оцінюються зверху вниз під час генерації посилань:

// ПОМИЛКА: генерує посилання на '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');

Ми не будемо приховувати від вас, що для правильної побудови списку потрібна певна навичка. Поки ви не навчитеся, панель routing panel буде корисним інструментом.

Маска і параметри

Маска описує відносний шлях, заснований на корені сайту. Найпростіша маска – це статичний URL:

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

Часто маски містять так звані параметри. Вони укладені в кутові дужки (наприклад, <year>) і передаються в цільовий презентер, наприклад, у метод renderShow(int $year) або в постійний параметр $year:

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

У прикладі йдеться про те, що якщо ми відкриємо https://any-domain.com/chronicle/2020 і дія show з параметром year: 2020 відображатиметься.

Ми можемо вказати значення за замовчуванням для параметрів безпосередньо в масці, і таким чином вона стає необов'язковою:

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

Тепер маршрут прийматиме URL https://any-domain.com/chronicle/ з параметром year: 2020`.

Звісно, ім'я презентера та дія також можуть бути параметрами. Наприклад:

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

Цей маршрут приймає, наприклад, URL у формі /article/edit і /catalog/list відповідно, і переводить їх у презентери та дії Article:edit і Catalog:list відповідно.

Він також надає параметрам presenter і action значення за замовчуванням Home і default і тому вони є необов'язковими. Тому маршрут також приймає URL /article і переводить його як Article:default. Або навпаки, посилання на Product:default генерує шлях /product, посилання на стандартну Home:default генерує шлях /.

Маска може описувати не тільки відносний шлях, що базується на корені сайту, а й абсолютний шлях, якщо він починається зі слеша, або навіть увесь абсолютний URL, якщо він починається з двох слешів:

// відносний шлях до кореня програми
$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+ regexp:

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

За замовчуванням регулярним виразом для всіх параметрів є [^/]+, тобто все, крім слеша. Якщо параметр має відповідати також косій рисі, ми задаємо регулярний вираз .+.

// приймає https://example.com/a/b/c, path - 'a/b/c'
$router->addRoute('<path .+>', /* ... */);

Необов'язкові послідовності

Квадратні дужки позначають необов'язкові частини маски. Будь-яка частина маски може бути задана як необов'язкова, включно з тими, які містять параметри:

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

// URL-адреси, що приймаються: Параметри:
//   /en/download           lang => en, 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',
);

// URL-адреси, що приймаються:
//   /ru/hello
//   /en-us/hello
//   /hello
//   /hello/page-12

Генератор URL намагається зробити URL якомога коротшим, тому те, що можна опустити, опускається. Тому, наприклад, маршрут 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% = домен верхнього рівня, наприклад, com або org
  • %sld% = домен другого рівня, наприклад, example
  • %domain% = домен без піддоменів, наприклад, example.com
  • %host% = весь хост, наприклад, www.example.com
  • %basePath% = шлях до кореневого каталогу
$router->addRoute('//www.%domain%/%basePath%/<presenter>/<action>', /* ... */);
$router->addRoute('//www.%sld%.%tld%/%basePath%/<presenter>/<action', /* ... */);

Розширена нотація

Другий параметр маршруту, який ми часто пишемо у форматі Presenter:action, є абревіатурою, яку ми також можемо написати у вигляді поля, де ми безпосередньо вказуємо значення (за замовчуванням) окремих параметрів:

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

Або ми можемо використовувати цю форму, зверніть увагу на переписування регулярного виразу перевірки:

use Nette\Routing\Route;

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

Ці детальніші формати корисні для додавання додаткових метаданих.

Фільтри та переклади

Доброю практикою є написання вихідного коду англійською мовою, але що якщо вам потрібно, щоб URL вашого сайту було перекладено іншою мовою?

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

буде генерувати англійські URL, такі як /product/123 або /cart. Якщо ми хочемо, щоб презентери та дії в URL були перекладені німецькою мовою (наприклад, /produkt/123 або /einkaufswagen), ми можемо використовувати словник перекладів. Щоб додати його, нам уже потрібен “більш зрозумілий” варіант другого параметра:

use Nette\Routing\Route;

$router->addRoute('<presenter>/<action>', [
	'presenter' => [
		Route::Value => 'Home',
		Route::FilterTable => [
			// строка в URL => ведущий
			'produkt' => 'Product',
			'einkaufswagen' => 'Cart',
			'katalog' => 'Catalog',
		],
	],
	'action' => [
		Route::Value => 'default',
		Route::FilterTable => [
			'liste' => 'list',
		],
	],
]);

Для одного і того ж презентера можна використовувати кілька ключів словника. Вони створюватимуть для нього різні псевдоніми. Останній ключ вважається канонічним варіантом (тобто той, який буде в згенерованому URL).

Таблиця перекладу може бути застосована до будь-якого параметра таким чином. Однак якщо перекладу не існує, береться вихідне значення. Ми можемо змінити цю поведінку, додавши Route::FilterStrict => true, і тоді маршрут відхилятиме 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 і рядком, який потім передають презентувальнику, функція FilterOut забезпечує перетворення у зворотному напрямку.

Параметри presenter, action і module вже мають зумовлені фільтри, які конвертують між PascalCase, camelCase і kebab-case відповідно, що використовуються в URL. Значення параметрів за замовчуванням уже записано в перетвореній формі, тому, наприклад, у випадку з презентером ми пишемо <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 { /* ... */ },
	],
]);

Загальні фільтри дають вам можливість налаштувати поведінку маршруту абсолютно будь-яким способом. Ми можемо використовувати їх, наприклад, для зміни параметрів на основі інших параметрів. Наприклад, переведення <presenter> и <action> на основі поточного значення параметра <lang>.

Якщо для параметра визначено користувацький фільтр і одночасно існує загальний фільтр, користувацький FilterIn виконується перед загальним, і навпаки, загальний FilterOut виконується перед користувацьким. Таким чином, усередині загального фільтра знаходяться значення параметрів presenter і action відповідно, написані мовою PascalCase і camelCase відповідно.

Прапор OneWay

Односторонні маршрути використовуються для збереження функціональності старих 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-адреси презентер автоматично перенаправляє на нову URL-адресу, щоб пошукові системи не індексували ці сторінки двічі (див. SEO та канонізація).

Динамічна маршрутизація із зворотними дзвінками

Динамічна маршрутизація з функцією зворотного виклику дозволяє безпосередньо призначати маршрутам функції (зворотні виклики), які будуть виконуватися при відвідуванні вказаного шляху. Ця гнучка функція дозволяє швидко та ефективно створювати різні кінцеві точки для вашого додатку:

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

Ви також можете визначити параметри в масці, які будуть автоматично передані вашому зворотному виклику:

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

Модулі

Якщо у нас є кілька маршрутів, що належать одному модулю, ми можемо використовувати withModule() для їхнього групування:

$router = new RouteList;
$router->withModule('Forum') // наступні маршрутизатори входять до складу модуля Forum
	->addRoute('rss', 'Feed:rss') // презентер Forum:Feed
	->addRoute('<presenter>/<action>')

	->withModule('Admin') // наступні маршрутизатори є частиною модуля Forum:Admin
		->addRoute('sign:in', 'Sign:in');

Альтернативою є використання параметра module:

// URL manage/dashboard/default відображається на ведучого Admin:Dashboard
$router->addRoute('manage/<presenter>/<action>', [
	'module' => 'Admin',
]);

Субдомени

Колекції маршрутів можуть бути згруповані за піддоменами:

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

Ви також можете використовувати Символи підстановки у своєму доменному імені:

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

Префікс шляху

Колекції маршрутів можуть бути згруповані за шляхом в 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(/* ... */)
			// ...

Параметри запиту

Маски також можуть містити параметри запиту (параметри після знака питання в URL). Вони не можуть визначити вираз перевірки, але вони можуть змінити ім'я, під яким вони передаються презентувальнику:

// використовуємо параметр запиту 'cat' як 'categoryId' у застосунку
$router->addRoute('product ? id=<productId> & cat=<categoryId>', /* ... */);

Параметри Foo

Тепер ми йдемо глибше. Параметри Foo – це, по суті, неіменовані параметри, які дозволяють зіставити регулярний вираз. Наступний маршрут відповідає /index, /index.html, /index.htm і /index.php:

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

Також можна явно задати рядок, який буде використовуватися для генерації URL. Рядок має розташовуватися безпосередньо після знака запитання. Наступний маршрут схожий на попередній, але генерує /index.html замість /index, тому що рядок .html встановлено як “значення, що генерується”.

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

Інтеграція

Щоб підключити наш маршрутизатор до застосунку, ми повинні повідомити про нього контейнер DI. Найпростіший спосіб – це підготувати фабрику, яка буде створювати об'єкт маршрутизатора, і повідомити конфігурацію контейнера, щоб вона його використовувала. Припустимо, ми напишемо для цього метод 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;
	}
}

Потім ми пишемо в configuration:

services:
	- App\Core\RouterFactory::createRouter

Будь-які залежності, такі як підключення до бази даних тощо, передаються методу фабрики як параметри за допомогою autowiring:

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

SimpleRouter

Набагато простішим маршрутизатором, ніж колекція маршрутів, є SimpleRouter. Його можна використовувати, коли немає потреби в певному форматі URL, коли mod_rewrite (або альтернативи) недоступний або коли ми просто не хочемо поки що возитися зі зручними для користувача URL.

Генерує адреси приблизно такої форми:

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

Параметром конструктора SimpleRouter є презентер і дія за замовчуванням, тобто дія, яку буде виконано, якщо ми відкриємо, наприклад, http://example.com/ без додаткових параметрів.

// використовуємо презентер 'Home' і дію 'default'
$router = new Nette\Application\Routers\SimpleRouter('Home:default');

Ми рекомендуємо визначати SimpleRouter безпосередньо в конфігурації:

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

SEO та канонізація

Фреймворк покращує SEO, запобігаючи дублюванню контенту на різних URL. Якщо кілька адрес посилаються на одне й те саме місце призначення, наприклад /index і /index.html, фреймворк визначає першу з них як основну (канонічну) і перенаправляє на неї решту за допомогою HTTP-коду 301. Завдяки цьому пошукові системи не будуть індексувати сторінки двічі і не порушать їхній сторінковий рейтинг.

Цей процес називається канонізацією. Канонічний URL – це URL, згенерований маршрутизатором, тобто першим відповідним маршрутом у колекції без прапора OneWay. Тому в колекції ми перераховуємо первинні маршрути першими.

Канонізація здійснюється презентером, докладніше в розділі Канонізація.

HTTPS

Для того щоб використовувати протокол HTTPS, необхідно активувати його на хостингу та налаштувати сервер.

Перенаправлення всього сайту на HTTPS має бути виконано на рівні сервера, наприклад, за допомогою файлу .htaccess у кореневому каталозі нашого застосунку, з HTTP-кодом 301. Налаштування можуть відрізнятися залежно від хостингу і виглядають приблизно так:

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

Маршрутизатор генерує URL з тим самим протоколом, за яким було завантажено сторінку, тому немає необхідності задавати що-небудь ще.

Однак, якщо нам виключно потрібно, щоб різні маршрути працювали під різними протоколами, ми помістимо це в маску маршруту:

// Згенерує HTTP-адресу
$router->addRoute('http://%host%/<presenter>/<action>', /* ... */);

// Згенерує HTTPS адресу
$router->addRoute('https://%host%/<presenter>/<action>', /* ... */);

Налагоджувач маршрутизації

Смуга маршрутизації, показана в Tracy Bar, є корисним інструментом, який відображає список маршрутів, а також параметри, які маршрутизатор отримав з URL.

Зелена смуга з символом ✓ представляє маршрут, який відповідає поточному URL, сині смуги з символами ≈ вказують на маршрути, які також відповідали б URL, якби зелений колір не обігнав їх. Далі ми бачимо поточного ведучого та дії.

Водночас, якщо відбувається несподіване перенаправлення через канонікалізацію, корисно зазирнути в панель redirect, щоб дізнатися, як маршрутизатор спочатку зрозумів URL і чому він перенаправив.

Під час налагодження маршрутизатора рекомендується відкрити Developer Tools у браузері (Ctrl+Shift+I або Cmd+Option+I) і вимкнути кеш у панелі Network, щоб перенаправлення не зберігалися в ньому.

Продуктивність

Кількість маршрутів впливає на швидкість маршрутизатора. Їхня кількість, звісно, не повинна перевищувати кількох десятків. Якщо ваш сайт має занадто складну структуру URL, ви можете написати Користувацький маршрутизатор.

Якщо маршрутизатор не має залежностей, наприклад, від бази даних, і його фабрика не має аргументів, ми можемо серіалізувати його скомпільовану форму безпосередньо в DI-контейнер і таким чином зробити додаток трохи швидшим.

routing:
	cache: true

Користувацький маршрутизатор

Наступні рядки призначені для дуже досвідчених користувачів. Ви можете створити свій власний маршрутизатор і, природно, додати його до колекції маршрутів. Маршрутизатор являє собою реалізацію інтерфейсу 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, а й заголовки тощо, у масив, що містить ім'я ведучого і його параметри. Якщо він не може обробити запит, то повертає null. Під час обробки запиту ми повинні повернути щонайменше ведучого і дію. Ім'я ведучого є повним і включає будь-які модулі:

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

Метод constructUrl, з іншого боку, генерує абсолютний URL з масиву параметрів. Він може використовувати інформацію з параметра $refUrl, який є поточним URL.

Щоб додати користувацький маршрутизатор до колекції маршрутів, використовуйте add():

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

Роздільне використання

Під роздільним використанням мається на увазі використання можливостей маршрутизатора в додатку, який не використовує додаток Nette і презентери. До нього можна застосувати майже все, що ми показали в цій главі, з такими відмінностями:

Отже, ми знову додамо метод, який буде створювати, наприклад, маршрутизатор:

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-контейнер, як ми рекомендуємо, додайте метод у конфігурацію ще раз, а потім отримайте маршрутизатор разом із HTTP-запитом із контейнера:

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

Тепер потрібно дати маршрутизатору попрацювати:

$params = $router->match($httpRequest);
if ($params === null) {
	// не знайдено співпадаючого маршруту, надішлемо помилку 404
	exit;
}

// обробляємо отримані параметри
$controller = $params['controller'];
// ...

І навпаки, ми будемо використовувати маршрутизатор для створення посилання:

$params = ['controller' => 'ArticleController', 'id' => 123];
$url = $router->constructUrl($params, $httpRequest->getUrl());
версію: 4.0