Компонент Маршрутизации

Компонент Маршрутизации

Перед тем, как мы нырнём в компонент Маршрутизации, давайте немножечко перепроектируем наш текущий фреймворк , чтобы сделать шаблоны ещё более читаемыми:

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 = array(
    '/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\RequestContext;
use Symfony\Component\Routing\Matcher\UrlMatcher;

$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
print_r($matcher->match('/bye'));
/* Gives:
array (
  '_route' => 'bye',
);
*/

print_r($matcher->match('/hello/Fabien'));
/* Gives:
array (
  'name' => 'Fabien',
  '_route' => 'hello',
);
*/

print_r($matcher->match('/hello'));
/* Gives:
array (
  '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 $e) {
    $response = new Response('Not Found', 404);
} catch (Exception $e) {
    $response = new Response('An error occurred', 500);
}

$response->send();

В коде существует несколько новых вещей:

  • Имена маршрутов используются для имён шаблонов;
  • Ошибки 500 теперь корректно обрабатываются;
  • Атрибуты запросов извлекаются для того, чтобы наши шаблоны оставались простыми:

    1
    2
    <!-- example.com/src/pages/hello.php -->
    Hello <?php echo 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}', array('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', array('name' => 'Fabien'));
// выводит /hello/Fabien

Код не должен требовать объяснений, и благодаря контексту, вы можете даже сгенерировать абсолютные URL:

1
2
3
4
5
6
7
8
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

echo $generator->generate(
    'hello',
    array('name' => 'Fabien'),
    UrlGeneratorInterface::ABSOLUTE_URL
);
// выводит что-то вроде http://example.com/somewhere/hello/Fabien

Tip

Волнуетесь о производительности? Основываясь на ваших определениях маршрутов, создайте высокооптимизированный класс сопоставителя URL, который может заменить UrlMatcher по умолчанию:

1
2
3
$dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes);

echo $dumper->dump();