# Маршрутизатор HTTP-запросов для PHP

Пакет HttpSoft\Router обеспечивает удобное управление маршрутизацией HTTP-запросов.

Этот пакет поддерживает интерфейсы PSR-7 и PSR-15.

Для этого пакета требуется PHP версии 7.4 или более поздней.

Установка пакета:

composer require httpsoft/http-router

Исходный код на GitHub.

# API

Исключения:

Посредники:

# Использование

use HttpSoft\Router\RouteCollector;

/**
 * @var mixed $handler
 */

$router = new RouteCollector();

# Определение маршрутов

$router->get('home', '/', $handler);
$router->post('logout', '/logout', $handler);
$router->add('login', '/login', $handler, ['GET', 'POST']);

$router->delete('post.delete', '/post/delete/{id}', $handler);
$router->get('post.view', '/post/{slug}{format}', $handler);

При добавлении параметра-плейсхолдера {token} в путь по умолчанию будет использоваться регулярное выражение [^/]+, которое соответствует любым символам, кроме слэша, указывающего на следующий сегмент пути.

Чтобы указать пользовательские регулярные выражения для плейсхолдеров, используйте метод HttpSoft\Router\Route::tokens().

$router->delete('post.delete', '/post/delete/{id}', $handler)->tokens(['id' => '\d+']);

$router->get('post.view', '/post/{slug}{format}', $handler)
    ->tokens(['slug' => '[\w\-]+', 'format' => '\.[a-zA-z]{3,}'])
;

Вы можете установить для токена значение по умолчанию, используя метод HttpSoft\Router\Route::defaults().

$router->get('post.view', '/post/{slug}{format}', $handler)
    ->tokens(['slug' => '[\w\-]+', 'format' => '\.[a-zA-z]{3,}'])
    ->defaults(['format' => '.html'])
;

// '/post/post-slug.html'
$router->routes()->path('post.view', ['slug' => 'post-slug']);

# Необязательные параметры

Токены маршрута, заключенные в [...], считаются необязательными.

$router->get('post.list', '/posts{[page]}', $handler)
    ->tokens(['page' =>  '\d+'])
;

// '/posts/33'
$router->routes()->path('post.list', ['page' => 33]);
// '/posts'
$router->routes()->path('post.list');

Необязательные параметры ДОЛЖНЫ находиться только в самом конце маршрута.

Обратите внимание, что ведущий слэш не нужно помещать перед токеном необязательного параметра.

Также можно установить значение по умолчанию для токена необязательного параметра.

$router->get('post.list', '/posts{[page]}', $handler)
    ->tokens(['page' =>  '\d+'])
    ->defaults(['page' =>  '1'])
;

// '/posts/1'
$router->routes()->path('post.list');
// '/posts'
$router->routes()->path('post.list', ['page' => null]);

Иногда бывает полезно иметь маршрут с несколькими необязательными параметрами подряд.

// `{[year/month/day]}` эквивалентно `{[/year/month/day]}`
$router->get('post.archive', '/post/archive{[year/month/day]}', $handler)
    ->tokens(['year' => '\d{4}', 'month' => '\d{2}', 'day' => '\d{2}'])
;

// '/post/archive'
$router->routes()->path('post.archive', ['year' => null, 'month' => null, 'day' => null]);
// или 
$router->routes()->path('post.archive');

// '/post/archive/2020/09/12'
$router->routes()->path('post.archive', ['year' => '2020', 'month' => '09', 'day' => '12']);
// '/post/archive/2020/09'
$router->routes()->path('post.archive', ['year' => '2020', 'month' => '09']);
// '/post/archive/2020'
$router->routes()->path('post.archive', ['year' => '2020']);

# Соответствие хоста

Используя метод HttpSoft\Router\Route::host(), вы можете указать конкретный хост для каждого маршрута.

// Только для example.com
$router->get('page', '/page', $handler)
    ->host('example.com')
;

// Только для subdomain.example.com
$router->get('page', '/page', $handler)
    ->host('subdomain.example.com')
;

// Только для shop.example.com или blog.example.com
$router->get('page', '/page', $handler)
    ->host('(shop|blog).example.com')
;

// Для любого поддомена
$router->get('page', '/page', $handler)
    ->host('(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?).example.com')
;

Обратите внимание, что все точки в регулярном выражении будут автоматически экранированы.

# Группы маршрутов

Можно указать маршруты внутри группы. Все маршруты, определенные внутри группы, будут иметь общий префикс пути.

$router->group('/post', static function (RouteCollector $router) use ($handler): void {
    // '/post/post-slug'
    $router->get('post.view', '/{slug}', $handler)->tokens(['slug' => '[\w-]+']);
    // '/post' or '/post/page-14'
    $router->get('post.list', '{[page]}', $handler)->tokens(['page' =>  'page-\d+']);
    // '/post/delete/13'
    $router->delete('post.delete', '/delete/{id}', DeleteHandler::class)->tokens(['id' => '\d+']);
    // '/post/create/11' or '/post/update/12'
    $router->add('post.edit', '/(create|update)/{id}', EditHandler::class, ['GET', 'POST'])
        ->tokens(['id' => '\d+'])
    ;
});

Результат будет эквивалентен:

$router->get('post.view', '/post/{slug}', $handler)->tokens(['slug' => '[\w-]+']);
$router->get('post.list', '/post{[page]}', $handler)->tokens(['page' =>  'page-\d+']);
$router->delete('post.delete', '/post/delete/{id}', DeleteHandler::class)->tokens(['id' => '\d+']);
$router->add('post.edit', '/post/(create|update)/{id}', CreateHandler::class, ['GET', 'POST'])
    ->tokens(['id' => '\d+'])
;

Также поддерживаются вложенные группы, в этом случае префиксы всех вложенных групп объединяются.

Подробнее о сопоставлении маршрутов смотрите в описании следующих классов:

# Генерация URI

Для генерации путей используйте метод HttpSoft\Router\RouteCollection::path().

$router->get('home', '/', $handler);
$router->routes()->path('home'); // '/'

$router->get('page', '/{slug}', $handler)->tokens(['slug' => '[\w\-]+']);
$router->routes()->path('page', ['slug' => 'page-slug']); // '/page-slug'

Для генерации URL используйте метод HttpSoft\Router\RouteCollection::url().

$router->get('home', '/', $handler)->host('(shop|blog).example.com');
$router->routes()->url('home', []); // '/'
$router->routes()->url('home', [], 'shop.example.com'); // '//shop.example.com'
$router->routes()->url('home', [], 'blog.example.com', true); // 'https://blog.example.com'
$router->routes()->url('home', [], 'shop.example.com', false); // 'http://shop.example.com'

$router->get('page', '/{slug}', $handler)->tokens(['slug' => '[\w\-]+']);
$router->routes()->url('page', ['slug' => 'page-slug']); // '/page-slug'
$router->routes()->url('page', ['slug' => 'page-slug'], 'any-host.com'); // '//any-host.com/page-slug'

Подробнее о сопоставлении маршрутов смотрите в описании следующих классов:

# Сопоставление маршрутов

Для сопоставления маршрутов рекомендуется использовать посредников.

В простейшем варианте ваше приложение может делать что-то вроде следующего:

/**
 * @var HttpSoft\Router\RouteCollector $router
 * @var Psr\Http\Message\ServerRequestInterface $request
 */

if (!$route = $router->routes()->match($request, false)) {
    // 404 Not Found
}

if (!$route->isAllowedMethod($request->getMethod())) {
    // 405 Method Not Allowed
}

// 200 OK
return $route;

Подробнее о сопоставлении маршрутов смотрите в описании следующих классов: