Компонент Routing
Дата оновлення перекладу 2023-06-22
Компонент Routing
Перед тим, як ми пірнемо в компонент Routing, давайте трошечки перепроектуємо наш поточний фреймворк, щоб зробити шаблони ще читанішими:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals();
$map = [
'/hello' => 'hello',
'/bye' => 'bye',
];
$path = $request->getPathInfo();
if (isset($map[$path])) {
ob_start();
extract($request->query->all(), EXTR_SKIP);
include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]);
$response = new Response(ob_get_clean());
} else {
$response = new Response('Not Found', 404);
}
$response->send();
Так як тепер ми вилучаємо параметри запиту, спростіть шаблон hello.php
наступним чином:
1 2
<!-- example.com/src/pages/hello.php -->
Hello <?php echo htmlspecialchars(isset($name) ? $name : 'World', ENT_QUOTES, 'UTF-8') ?>
Тепер ми готові до додавання нових функцій.
Одним дуже важливим аспектом будь-якогог сайту є форма його URL. Завдяки мапі
URL, ми відділили URL від коду, що генерує повʼязану відповідь, але він все ще
недостатньо гнучкий. Наприклад, ми можемо захотіти пдітримати динамічні шляхи,
щоб дозволити вбудовування даних напряму в URL (наприклад, /hello/Fabien
),
замість того, щоб покладатися на рядок запиту (наприклад, /hello?name=Fabien
).
Щоб підтримати цю функцію, додайте компонент Маршрутизація Symfony в якості залежності:
1
$ composer require symfony/routing
Замість масиву для мапи URL, компонент Маршрутизація покладається на екземпляр
RouteCollection
:
1 2 3
use Symfony\Component\Routing\RouteCollection;
$routes = new RouteCollection();
Давайте додамо маршрут, який описує URL /hello/SOMETHING
та додамо ще один
для простого /bye
:
1 2 3 4
use Symfony\Component\Routing\Route;
$routes->add('hello', new Route('/hello/{name}', array('name' => 'World')));
$routes->add('bye', new Route('/bye'));
Кожний запис у колекції визначається іменем (hello
) та екземпляром Route
,
який визначається схемою маршруту (/hello/{name}
) та масивом значень за замовчуванням
для атрибутів маршруту (array('name' => 'World')
).
Note
Прочитайте документацію компонента Маршрутизація, щоб дізнатися більше про його головні функції на кшталт генерування URL, вимог атрибутів, примусу HTTP-методів, завантажувачів для файлів YAML або XML, скидання правил виведення в PHP або Apache для покращеної продуктивності та багато іншого.
Засновуючись на інформації, що зберігається в екземплярі RouteCollection
, екземпляр
UrlMatcher
може співпадати зі шляхами URL:
1 2 3 4 5 6 7 8
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($routes, $context);
$attributes = $matcher->match($request->getPathInfo());
Метод match()
бере шлях запиту та повертає масив атрибутів (відмітьте, що маршрут,
що співпадає, автоматично зберігається у спеціальному атрибуті _route
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
$matcher->match('/bye');
/* Result:
[
'_route' => 'bye',
];
*/
$matcher->match('/hello/Fabien');
/* Result:
[
'name' => 'Fabien',
'_route' => 'hello',
];
*/
$matcher->match('/hello');
/* Result:
[
'name' => 'World',
'_route' => 'hello',
];
*/
Note
Навіть якщо нам не потрібен контекст запиту у наших прикладах, він використовується у реальних додатках для примусу вимог методів та іншого.
Зіставник URL видає виключення, коли не співпадає жоден маршрут:
1 2 3
$matcher->match('/not-found');
// видає Symfony\Component\Routing\Exception\ResourceNotFoundException
С цими знаннями у голові, давайте напишемо нову версію нашого фреймворку:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
try {
extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
$response = new Response(ob_get_clean());
} catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
} catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
$response->send();
В коді існує декілька нових речей:
- Імена маршрутів використовуються для імен шаблонів;
- Помилки
500
тепер коректно обробляються; - Атрибути запитів вилучаються для того, щоб наші шаблони залишалися простими:
1 2
// example.com/src/pages/hello.php
Hello <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
Конфігурація маршруту була переміщена у власний файл:
1 2 3 4 5 6 7 8
// example.com/src/app.php use Symfony\Component\Routing; $routes = new Routing\RouteCollection(); $routes->add('hello', new Routing\Route('/hello/{name}', ['name' => 'World'])); $routes->add('bye', new Routing\Route('/bye')); return $routes;
Тепер у нас є чітке розділення між конфігурацією (все, що відноситься до нашого додатку у
app.php
) та фреймворком (загальний код, який живить наш додаток уfront.php
).
З менше, ніж 30 рядками коду, у нас є новий фреймворк, потужніший та гнучкіший, ніж попередній. Насолоджуйтесь!
Використання компонента Маршрутизація має один великий додатковий плюс: здатність генерувати URL, засновуючись на визначеннях Маршруту. При використанні і співпадінні URL та генерування URL у вашому коді, наявність схем URL не повинна мати ніякого іншого впливу. Хочете дізнатися, як використовувати генератор? Надзвичайно просто:
1 2 3 4 5 6
use Symfony\Component\Routing;
$generator = new Routing\Generator\UrlGenerator($routes, $context);
echo $generator->generate('hello', ['name' => 'Fabien']);
// виводить /hello/Fabien
Код не повинен вимагати пояснень і, завдяки контексту, ви можете навіть згенерувати абсолютні URL:
1 2 3 4 5 6 7 8
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
echo $generator->generate(
'hello',
['name' => 'Fabien'],
UrlGeneratorInterface::ABSOLUTE_URL
);
// виводить щось типу http://example.com/somewhere/hello/Fabien
Tip
Хвилюєтеся про продуктивність? Засновуючись на ваших визначеннях маршрутів, створіть
вискооптимізований клас зіставника URL, який може замінити UrlMatcher
за замовчуванням:
1 2 3 4 5 6 7 8
use Symfony\Component\Routing\Matcher\CompiledUrlMatcher;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;
// $compiledRoutes - це простий PHP-масив, який описує всі маршрути у ефективному форматі даних
// ви можете (та маєте) кешувати його, зазвичай шляхом експорту до PHP-файлу
$compiledRoutes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes();
$matcher = new CompiledUrlMatcher($compiledRoutes, $context);