# Настройка маршрутов

За маршрутизацию отвечает пакет HttpSoft\Router, а экземпляр приложения проксирует методы сборщика маршрутов.

По умолчанию настройка маршрутов производится в файле config/routes.php. Предоставляется несколько методов добавления маршрутов для наиболее популярных HTTP-методов. Каждый из этих методов создает, добавляет в коллекцию и возвращает экземпляр HttpSoft\Router\Route.

/**
 * @var HttpSoft\Basis\Application $app
 * @var Psr\Container\ContainerInterface $container
 * @var mixed $handler
 */

$app = $container->get(HttpSoft\Basis\Application::class);

// Только для метода "GET":
$app->get('post.view', '/posts/{id}', $handler)->tokens(['id' => '\d+']);

// Только для метода "POST":
$app->post('post.create', '/posts', $handler);

// Только для метода "PUT":
$app->post('post.update', '/posts/{id}', $handler)->tokens(['id' => '\d+']);

// Только для метода "PATCH":
$app->patch('post.change', '/posts/{id}', $handler)->tokens(['id' => '\d+']);

// Только для метода "DELETE":
$app->delete('post.delete', '/posts/{id}', $handler)->tokens(['id' => '\d+']);

// Только для метода "HEAD":
$app->delete('post.headers', '/posts', $handler);

// Только для метода "OPTIONS":
$app->options('post.options', '/posts', $handler);

// Для любых методов:
$app->any('posts', '/posts{[id]}', $handler)->tokens(['id' => '\d+']);

// Для пользовательских методов:
$app->add('posts.edit', '/posts/(create|update)/{id}', $handler, ['GET', 'POST'])
    ->tokens(['id' => '\d+'])
;

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

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

use HttpSoft\Router\RouteCollector;

$app->group('/posts', static function (RouteCollector $router) use ($handler): void {
    $router->get('post.view', '/{slug}', $handler)->tokens(['slug' => '[\w-]+']);
    $router->get('post.list', '{[page]}', $handler)->tokens(['page' => '\d+']);
});

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

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

# Плейсхолдеры и токены

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

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

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

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

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

Плейсхолдеры, заключенные в [...], считаются необязательными.

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

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

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

Читайте подробнее о добавлении маршрутов, установке хоста для каждого маршрута, токенах и т.д.

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

Процесс сопоставления маршрутов происходит в два этапа при помощи посредников (смотрите описание).

  1. HttpSoft\Router\Middleware\RouteMatchMiddleware — сопоставляет входящий запрос с добавленными маршрутами. При совпадении регистрирует экземпляр HttpSoft\Router\Route как атрибут запроса, используя имя класса HttpSoft\Router\Route в качестве имени атрибута, и передает запрос следующему посреднику.
  2. HttpSoft\Router\Middleware\RouteDispatchMiddleware — проверяет наличие совпадающего маршрута (экземпляра HttpSoft\Router\Route) в качестве атрибута в запросе. Если он существует, то используется обработчик этого маршрута, иначе обработка запроса делегируется обработчику, который передается аргументом в метод process().

Посредники регистрируются в конвейере config/pipeline.php. Между этими двумя посредниками может быть расположено любое количество других посредников. При этом HttpSoft\Router\Middleware\RouteDispatchMiddleware ДОЛЖЕН находиться в конце конвейера.

Читайте подробнее о конвейере посредников.

# Обработка маршрутов

Для примера добавим два простых маршрута.

$app->get('home', '/', HomeAction::class);
$app->get('data', '/data', DataAction::class);

В качестве обработчиков используются вымышленные имена классов (HomeAction, DataAction и т.д.). Такой подход позволяет организовывать обработку каждого маршрута в отдельном классе. Эти классы реализуют Psr\Http\Server\RequestHandlerInterface.

use HttpSoft\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class HomeAction implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new JsonResponse(['name' => 'HomeAction']);
    }
}

Объекты обработчиков будут созданы через контейнер, поэтому они могут принимать зависимости в конструкторе.

use HttpSoft\Basis\Response\PrepareJsonDataTrait;
use HttpSoft\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class DataAction implements RequestHandlerInterface
{
    use PrepareJsonDataTrait;

    private Repository $repository;

    public function __construct(Repository $repository)
    {
        $this->repository = $repository;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new JsonResponse($this->prepareJsonData($this->repository->getAll()));
    }
}

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

Также обработчиком может быть:

Подробнее об обработчиках смотрите в HttpSoft\Runner\MiddlewareResolver.

# Генерация URI

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

use HttpSoft\Response\RedirectResponse;
use HttpSoft\Router\RouteCollector;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class PageAction implements RequestHandlerInterface
{
    private RouteCollector $router;
    
    public function __construct(RouteCollector $router)
    {
        $this->router = $router;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new RedirectResponse($this->router->routes()->path('home'));
    }
}

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

/**
 * @var HttpSoft\Router\RouteCollector $router
 * @var mixed $handler
 */

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

/**
 * @var HttpSoft\Router\RouteCollector $router
 * @var mixed $handler
 */

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

Подробнее о генерации URI смотрите в HttpSoft\Router\RouteCollection.