<? phpukraine ДОКУМЕНТАЦІЯ
⌕Пошук по платформі
Документація українською

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

ВЕРСІЯ
АКТУАЛЬНА Актуальний реліз. Переклад наздоганяє оригінал, розділи з низьким відсотком позначені у змісті.
ОСНОВИ · ПЕРЕКЛАДЕНО оновлено 18 вересня 2026

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

Коли ваш застосунок отримує запит, він викликає дію контролера, щоб згенерувати відповідь. Конфігурація маршрутизації визначає, яку дію запустити для кожного вхідного URL. Вона також надає інші корисні можливості, як-от генерацію SEO-дружніх URL (наприклад, /read/intro-to-symfony замість index.php?article_id=57).

Створення маршрутів

Маршрути можна налаштувати в YAML, PHP або за допомогою атрибутів. Усі формати надають однакові можливості та продуктивність, тож обирайте той, що вам до вподоби. Symfony рекомендує атрибути, бо зручно тримати маршрут і контролер в одному місці.

Створення маршрутів через атрибути

PHP-атрибути дозволяють визначати маршрути поруч із кодом контролерів, повʼязаних із цими маршрутами. Атрибути ввімкнені за замовчуванням у застосунках Symfony, які використовують Symfony Flex, тож ви можете починати користуватися ними одразу.

Примітка. Якщо ваш проєкт не використовує Symfony Flex, ви маєте увімкнути маршрутизацію через атрибути вручну, створивши такий конфігураційний файл:

# config/routes.yaml
controllers:
    resource: routing.controllers

Це вказує Symfony шукати атрибути #[Route] по всьому застосунку й реєструвати як маршрути, так і повʼязані з ними контролери.

Припустімо, ви хочете визначити маршрут для URL /blog у своєму застосунку. Для цього створіть клас контролера на кшталт такого:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog', name: 'blog_list')]
    public function list(): Response
    {
        // ...
    }
}

Ця конфігурація визначає маршрут із назвою blog_list, який спрацьовує, коли користувач запитує URL /blog. Коли збіг стається, застосунок виконує метод list() класу BlogController.

Примітка. Рядок запиту (query string) URL не враховується під час зіставлення маршрутів. У цьому прикладі URL на кшталт /blog?foo=bar та /blog?foo=bar&bar=foo теж збігатимуться з маршрутом blog_list.

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

Назва маршруту (blog_list) поки що не важлива, але вона стане суттєвою пізніше, коли ви генеруватимете URL. Вам треба лише памʼятати, що кожна назва маршруту має бути унікальною в застосунку.

Створення маршрутів у YAML- або PHP-файлах

Замість визначення маршрутів у класах контролерів ви можете визначити їх в окремому YAML- або PHP-файлі. Головна перевага в тому, що вони не потребують жодної додаткової залежності. Головний недолік — доводиться працювати з кількома файлами, коли перевіряєш маршрутизацію якоїсь дії контролера.

Наступний приклад показує, як визначити у YAML або PHP маршрут із назвою blog_list, що повʼязує URL /blog із дією list() контролера BlogController:

# config/routes.yaml
blog_list:
    path: /blog
    # значення controller має формат 'controller_class::method_name'
    controller: App\Controller\BlogController::list

    # якщо дію реалізовано як метод __invoke() класу контролера,
    # частину '::method_name' можна пропустити:
    # controller: App\Controller\BlogController
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;

return Routes::config([
    'blog_list' => [
        'path' => '/blog',
        // значення controller має формат [controller_class, method_name]
        'controller' => [BlogController::class, 'list'],

        // якщо дію реалізовано як метод __invoke() класу контролера,
        // частину 'method_name' можна пропустити:
        // 'controller' => BlogController::class,
    ],
]);

Зіставлення HTTP-методів

За замовчуванням маршрути збігаються з будь-яким HTTP-дієсловом (GET, POST, PUT тощо). Використовуйте опцію methods, щоб обмежити дієслова, на які має відповідати кожен маршрут:

// src/Controller/BlogApiController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogApiController extends AbstractController
{
    #[Route('/api/posts/{id}', methods: ['GET', 'HEAD'])]
    public function show(int $id): Response
    {
        // ... повертає JSON-відповідь із постом
    }

    #[Route('/api/posts/{id}', methods: ['PUT'])]
    public function edit(int $id): Response
    {
        // ... редагує пост
    }
}
# config/routes.yaml
api_post_show:
    path:       /api/posts/{id}
    controller: App\Controller\BlogApiController::show
    methods:    GET|HEAD

api_post_edit:
    path:       /api/posts/{id}
    controller: App\Controller\BlogApiController::edit
    methods:    PUT
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogApiController;

return Routes::config([
    'api_post_show' => [
        'path' => '/api/posts/{id}',
        'controller' => [BlogApiController::class, 'show'],
        'methods' => ['GET', 'HEAD'],
    ],
    'api_post_edit' => [
        'path' => '/api/posts/{id}',
        'controller' => [BlogApiController::class, 'edit'],
        'methods' => ['PUT'],
    ],
]);

Порада. HTML-форми підтримують лише методи GET і POST. Якщо ви викликаєте маршрут з іншим методом із HTML-форми, додайте приховане поле з назвою _method і потрібним методом (наприклад, <input type="hidden" name="_method" value="PUT">). Якщо ви створюєте форми за допомогою Symfony Forms, це робиться автоматично, коли опція framework.http_method_override має значення true.

З міркувань безпеки ви можете обмежити, які HTTP-методи можна перевизначати, за допомогою опції framework.allowed_http_method_override.

Зіставлення оточень

Використовуйте опцію env, щоб зареєструвати маршрут лише тоді, коли поточне оточення конфігурації (configuration environment) відповідає заданому значенню:

// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class DefaultController extends AbstractController
{
    #[Route('/tools', name: 'tools', env: 'dev')]
    public function developerTools(): Response
    {
        // ...
    }

    // Ви також можете передати масив оточень
    #[Route('/tools', name: 'tools', env: ['dev', 'test'])]
    public function developerTools(): Response
    {
        // ...
    }
}
# config/routes.yaml
when@dev:
    tools:
        path: /tools
        controller: App\Controller\DefaultController::developerTools
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\DefaultController;

return Routes::config([
    'when@dev' => [
        'tools' => [
            'path' => '/tools',
            'controller' => [DefaultController::class, 'developerTools'],
        ],
    ],
]);

Зіставлення за виразами

Використовуйте опцію condition, якщо вам потрібно, щоб якийсь маршрут збігався на основі довільної логіки зіставлення:

// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class DefaultController extends AbstractController
{
    #[Route(
        '/contact',
        name: 'contact',
        condition: "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'",
        // вирази також можуть містити параметри конфігурації:
        // condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
    )]
    public function contact(): Response
    {
        // ...
    }

    #[Route(
        '/posts/{id}',
        name: 'post_show',
        // вирази можуть отримувати значення параметрів маршруту через змінну "params"
        condition: "params['id'] < 1000"
    )]
    public function showPost(int $id): Response
    {
        // ... повертає JSON-відповідь із постом
    }
}
# config/routes.yaml
contact:
    path:       /contact
    controller: App\Controller\DefaultController::contact
    condition:  "context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'"
    # вирази також можуть містити параметри конфігурації:
    # condition: "request.headers.get('User-Agent') matches '%app.allowed_browsers%'"
    # вирази можуть навіть використовувати змінні оточення:
    # condition: "context.getHost() == env('APP_MAIN_HOST')"

post_show:
    path:       /posts/{id}
    controller: App\Controller\DefaultController::showPost
    # вирази можуть отримувати значення параметрів маршруту через змінну "params"
    condition:  "params['id'] < 1000"
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\DefaultController;

return Routes::config([
    'contact' => [
        'path' => '/contact',
        'controller' => [DefaultController::class, 'contact'],
        'condition' => 'context.getMethod() in ["GET", "HEAD"] and request.headers.get("User-Agent") matches "/firefox/i"',
        // вирази також можуть містити параметри конфігурації:
        // 'condition' => 'request.headers.get("User-Agent") matches "%app.allowed_browsers%"',
        // вирази можуть навіть використовувати змінні оточення:
        // 'condition' => 'context.getHost() == env("APP_MAIN_HOST")',
    ],
    'post_show' => [
        'path' => '/posts/{id}',
        'controller' => [DefaultController::class, 'showPost'],
        // вирази можуть отримувати значення параметрів маршруту через змінну "params"
        'condition' => 'params["id"] < 1000',
    ],
]);

Значення опції condition — це вираз, що використовує будь-який коректний синтаксис мови виразів (expression language), і може використовувати будь-яку з цих змінних, створених Symfony:

context : Екземпляр Symfony\Component\Routing\RequestContext, який містить найбазовішу інформацію про маршрут, що зіставляється.

request : Обʼєкт Symfony Request, який представляє поточний запит.

params : Масив зіставлених параметрів маршруту для поточного маршруту.

Ви також можете використовувати такі функції:

env(string $name) : Повертає значення змінної за допомогою обробників змінних оточення

service(string $alias) : Повертає сервіс умови маршрутизації (routing condition service).

Спершу додайте атрибут #[AsRoutingConditionService] або тег routing.condition_service до сервісів, які ви хочете використовувати в умовах маршрутів:

use Symfony\Bundle\FrameworkBundle\Routing\Attribute\AsRoutingConditionService;
use Symfony\Component\HttpFoundation\Request;

#[AsRoutingConditionService(alias: 'route_checker')]
class RouteChecker
{
    public function check(Request $request): bool
    {
        // ...
    }
}

Далі використовуйте функцію service(), щоб послатися на цей сервіс усередині умов:

// Контролер (з використанням аліаса):
#[Route(condition: "service('route_checker').check(request)")]
// Або без аліаса:
#[Route(condition: "service('App\\\Service\\\RouteChecker').check(request)")]

Усередині вирази компілюються у чистий PHP. Через це використання ключа condition не створює жодних додаткових накладних витрат, окрім часу, потрібного на виконання відповідного PHP.

Увага! Умови не враховуються під час генерації URL (це пояснюється далі в цій статті).

Налагодження маршрутів

Коли ваш застосунок росте, у вас зрештою буде багато маршрутів. Symfony містить кілька команд, які допомагають налагоджувати проблеми з маршрутизацією. Спершу команда debug:router виводить усі маршрути вашого застосунку в тому самому порядку, в якому Symfony їх обчислює:

$ php bin/console debug:router

----------------  -------  --------------------------------------------
Name              Method   Path
----------------  -------  --------------------------------------------
homepage          ANY      /
contact           GET      /contact
contact_process   POST     /contact
article_show      ANY      /articles/{_locale}/{year}/{title}.{_format}
blog              ANY      /blog/{page}
blog_show         ANY      /blog/{slug}
----------------  -------  --------------------------------------------

# передайте цю опцію, щоб також показати всі визначені аліаси маршрутів
$ php bin/console debug:router --show-aliases

# передайте цю опцію, щоб також показати контролери, повʼязані з маршрутами
$ php bin/console debug:router --show-controllers

# передайте цю опцію, щоб показати лише маршрути, які відповідають заданому HTTP-методу
# (можна використати спеціальне значення ANY, щоб побачити маршрути, які відповідають будь-якому методу)
$ php bin/console debug:router --method=GET
$ php bin/console debug:router --method=ANY

# передайте цю опцію, щоб відсортувати список маршрутів за заданим стовпцем
$ php bin/console debug:router --sort=path
$ php bin/console debug:router --sort=name

Нове у версії 8.1. Опцію --sort для debug:router було введено в Symfony 8.1.

Передайте цьому аргументу назву (або частину назви) якогось маршруту, щоб вивести подробиці маршруту:

$ php bin/console debug:router app_lucky_number

+-------------+---------------------------------------------------------+
| Property    | Value                                                   |
+-------------+---------------------------------------------------------+
| Route Name  | app_lucky_number                                        |
| Path        | /lucky/number/{max}                                     |
| ...         | ...                                                     |
| Options     | compiler_class: Symfony\Component\Routing\RouteCompiler |
|             | utf8: true                                              |
+-------------+---------------------------------------------------------+

Інша команда називається router:match, і вона показує, який маршрут збігатиметься із заданим URL. Вона корисна, щоб зʼясувати, чому якийсь URL не виконує ту дію контролера, на яку ви очікуєте:

$ php bin/console router:match /lucky/number/8

  [OK] Route "app_lucky_number" matches

Параметри маршруту

У попередніх прикладах визначалися маршрути, де URL ніколи не змінюється (наприклад, /blog). Однак часто визначають маршрути, де деякі частини є змінними. Наприклад, URL для показу якогось допису в блозі, ймовірно, включатиме заголовок або слаг (наприклад, /blog/my-first-post чи /blog/all-about-symfony).

У маршрутах Symfony змінні частини загортаються у { }. Наприклад, маршрут для показу вмісту допису в блозі визначається як /blog/{slug}:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    // ...

    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show(string $slug): Response
    {
        // $slug дорівнюватиме динамічній частині URL
        // напр., за адресою /blog/yay-routing буде $slug='yay-routing'

        // ...
    }
}
# config/routes.yaml
blog_show:
    path:       /blog/{slug}
    controller: App\Controller\BlogController::show
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;

return Routes::config([
    'blog_show' => [
        'path' => '/blog/{slug}',
        'controller' => [BlogController::class, 'show'],
    ],
]);

Назва змінної частини ({slug} у цьому прикладі) використовується для створення PHP-змінної, у якій зберігається вміст цієї частини маршруту й передається до контролера. Якщо користувач відвідує URL /blog/my-first-post, Symfony виконує метод show() класу BlogController і передає методу show() аргумент $slug = 'my-first-post'.

Маршрути можуть визначати будь-яку кількість параметрів, але кожен із них можна використати лише один раз у кожному маршруті (наприклад, /blog/posts-about-{category}/page/{pageNumber}).

Валідація параметрів

Уявіть, що у вашому застосунку є маршрут blog_show (URL: /blog/{slug}) і маршрут blog_list (URL: /blog/{page}). Оскільки параметри маршруту приймають будь-яке значення, немає способу відрізнити ці два маршрути.

Якщо користувач запитує /blog/my-first-post, збігатимуться обидва маршрути, і Symfony використає той, який було визначено першим. Щоб це виправити, додайте якусь валідацію до параметра {page} за допомогою опції requirements:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '[0-9]+'])]
    public function list(int $page): Response
    {
        // ...
    }

    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show(string $slug): Response
    {
        // ...
    }
}
# config/routes.yaml
blog_list:
    path:       /blog/{page}
    controller: App\Controller\BlogController::list
    requirements:
        page: '[0-9]+'

blog_show:
    path:       /blog/{slug}
    controller: App\Controller\BlogController::show
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;

return Routes::config([
    'blog_list' => [
        'path' => '/blog/{page}',
        'controller' => [BlogController::class, 'list'],
        'requirements' => ['page' => '[0-9]+'],
    ],
    'blog_show' => [
        'path' => '/blog/{slug}',
        'controller' => [BlogController::class, 'show'],
    ],
]);

Опція requirements визначає регулярні вирази PHP, яким мають відповідати параметри маршруту, щоб увесь маршрут збігся. У цьому прикладі [0-9]+ — це регулярний вираз, який відповідає цифрам будь-якої довжини. Тепер:

URL Маршрут Параметри
/blog/2 blog_list $page = 2
/blog/my-first-post blog_show $slug = my-first-post

Порада. Перелік (enum) Symfony\Component\Routing\Requirement\Requirement містить набір поширених констант із регулярними виразами, як-от цифри, дати та UUID, які можна використовувати як вимоги до параметрів маршруту.

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => Requirement::DIGITS])]
    public function list(int $page): Response
    {
        // ...
    }
}
# config/routes.yaml
blog_list:
    path:       /blog/{page}
    controller: App\Controller\BlogController::list
    requirements:
        page: !php/const Symfony\Component\Routing\Requirement\Requirement::DIGITS
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;
use Symfony\Component\Routing\Requirement\Requirement;

return Routes::config([
    'blog_list' => [
        'path' => '/blog/{page}',
        'controller' => [BlogController::class, 'list'],
        'requirements' => ['page' => Requirement::DIGITS],
    ],
]);

Порада. Вимоги до маршрутів (а також шляхи маршрутів) можуть містити параметри конфігурації, що корисно, аби визначити складні регулярні вирази один раз і перевикористовувати їх у кількох маршрутах.

Порада. Параметри також підтримують властивості Unicode PCRE (PCRE Unicode properties) — escape-послідовності, що відповідають узагальненим типам символів. Наприклад, \p{Lu} відповідає будь-якому символу верхнього регістру будь-якою мовою, \p{Greek} відповідає будь-яким грецьким символам тощо.

Якщо вам так більше до вподоби, вимоги можна вписувати всередині кожного параметра за синтаксисом {parameter_name<requirements>}. Ця можливість робить конфігурацію лаконічнішою, але може погіршити читабельність маршруту, коли вимоги складні:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page<[0-9]+>}', name: 'blog_list')]
    public function list(int $page): Response
    {
        // ...
    }
}
# config/routes.yaml
blog_list:
    path:       /blog/{page<[0-9]+>}
    controller: App\Controller\BlogController::list
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;

return Routes::config([
    'blog_list' => [
        'path' => '/blog/{page<[0-9]+>}',
        'controller' => [BlogController::class, 'list'],
    ],
]);

Необовʼязкові параметри

У попередньому прикладі URL маршруту blog_list — це /blog/{page}. Якщо користувачі відвідують /blog/1, буде збіг. Але якщо вони відвідують /blog, збігу не буде. Щойно ви додаєте параметр до маршруту, він мусить мати значення.

Ви можете зробити так, щоб blog_list знову збігався, коли користувач відвідує /blog, додавши значення за замовчуванням для параметра {page}. Коли використовуються атрибути, значення за замовчуванням визначаються в аргументах дії контролера. В інших форматах конфігурації вони визначаються опцією defaults:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '[0-9]+'])]
    public function list(int $page = 1): Response
    {
        // ...
    }
}
# config/routes.yaml
blog_list:
    path:       /blog/{page}
    controller: App\Controller\BlogController::list
    defaults:
        page: 1
    requirements:
        page: '[0-9]+'

blog_show:
    # ...
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;

return Routes::config([
    'blog_list' => [
        'path' => '/blog/{page}',
        'controller' => [BlogController::class, 'list'],
        'defaults' => ['page' => 1],
        'requirements' => ['page' => '[0-9]+'],
    ],
    'blog_show' => [
        // ...
    ],
]);

Тепер, коли користувач відвідує /blog, збігатиметься маршрут blog_list, а $page за замовчуванням матиме значення 1.

Порада. Значенню за замовчуванням дозволено не відповідати вимозі.

Увага! Ви можете мати більше одного необовʼязкового параметра (наприклад, /blog/{slug}/{page}), але все, що йде після необовʼязкового параметра, теж має бути необовʼязковим. Наприклад, /{page}/blog — коректний шлях, але page завжди буде обовʼязковим (тобто /blog не збігатиметься з цим маршрутом).

Якщо ви хочете завжди включати якесь значення за замовчуванням у згенерований URL (наприклад, щоб примусово генерувати /blog/1 замість /blog у попередньому прикладі), додайте символ ! перед назвою параметра: /blog/{!page}

Як і у випадку з вимогами, значення за замовчуванням теж можна вписувати всередині кожного параметра за синтаксисом {parameter_name?default_value}. Ця можливість сумісна з вписаними вимогами, тож ви можете вписати обидва в один параметр:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page<[0-9]+>?1}', name: 'blog_list')]
    public function list(int $page): Response
    {
        // ...
    }
}
# config/routes.yaml
blog_list:
    path:       /blog/{page<[0-9]+>?1}
    controller: App\Controller\BlogController::list
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;

return Routes::config([
    'blog_list' => [
        'path' => '/blog/{page<[0-9]+>?1}',
        'controller' => [BlogController::class, 'list'],
    ],
]);

Порада. Щоб надати параметру значення за замовчуванням null, не додавайте нічого після символу ? (наприклад, /blog/{page?}). Якщо ви так робите, не забудьте оновити типи відповідних аргументів контролера, щоб дозволити передавання значень null (наприклад, замініть int $page на ?int $page).

Параметр priority

Symfony обчислює маршрути в порядку їх визначення. Якщо шлях маршруту відповідає багатьом різним патернам, це може завадити зіставленню інших маршрутів. У YAML- або PHP-файлах конфігурації ви можете переміщувати визначення маршрутів вище або нижче у файлі конфігурації, щоб керувати їхнім пріоритетом. У маршрутах, визначених як PHP-атрибути, це зробити значно важче, тож ви можете задати необовʼязковий параметр priority у таких маршрутах, щоб керувати їхнім пріоритетом:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    /**
     * Цей маршрут має «жадібний» патерн і визначений першим.
     */
    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show(string $slug): Response
    {
        // ...
    }

    /**
     * Цей маршрут не міг би збігтися без визначення пріоритету, вищого за 0.
     */
    #[Route('/blog/list', name: 'blog_list', priority: 2)]
    public function list(): Response
    {
        // ...
    }
}

Параметр priority очікує ціле значення. Маршрути з вищим пріоритетом сортуються раніше за маршрути з нижчим пріоритетом. Значення за замовчуванням, коли його не визначено, — 0.

Перетворення параметрів

Поширена потреба в маршрутизації — перетворити значення, збережене в якомусь параметрі (наприклад, ціле число, що є ID користувача), на інше значення (наприклад, обʼєкт, що представляє користувача). Ця можливість називається «param converter».

Тепер збережіть попередню конфігурацію маршруту, але змініть аргументи дії контролера. Замість string $slug додайте BlogPost $post:

// src/Controller/BlogController.php
namespace App\Controller;

use App\Entity\BlogPost;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    // ...

    #[Route('/blog/{slug:post}', name: 'blog_show')]
    public function show(BlogPost $post): Response
    {
        // $post — це обʼєкт, слаг якого збігається з параметром маршрутизації

        // ...
    }
}

Якщо аргументи вашого контролера містять type-hint для обʼєктів (BlogPost у цьому випадку), «param converter» робить запит до бази даних, щоб знайти обʼєкт за параметрами запиту (slug у цьому випадку). Якщо обʼєкт не знайдено, Symfony автоматично генерує відповідь 404.

Синтаксис {slug:post} зіставляє параметр маршруту з назвою slug з аргументом контролера з назвою $post. Він також підказує «param converter» знайти відповідний обʼєкт BlogPost у базі даних за слагом.

Коли ви зіставляєте кілька сутностей із параметрів маршруту, можуть виникати конфлікти назв. У цьому прикладі маршрут намагається визначити два зіставлення: одне для автора й одне для категорії; обидва використовують один і той самий параметр name. Це не дозволено, бо маршрут у підсумку оголошує name двічі:

#[Route('/search-book/{name:author}/{name:category}')]

Такі маршрути слід натомість визначати за допомогою такого синтаксису:

#[Route('/search-book/{authorName:author.name}/{categoryName:category.name}')]

Таким чином назви параметрів маршруту унікальні (authorName і categoryName), і «param converter» може коректно зіставити їх з аргументами контролера ($author і $category), завантаживши обидва за їхніми іменами.

Складніші зіставлення можна реалізувати за допомогою атрибута #[MapEntity]. Перегляньте документацію з перетворення параметрів у Doctrine, щоб дізнатися, як налаштувати запити до бази даних, які використовуються для отримання обʼєкта за параметром маршруту.

Параметри-переліки (backed enum)

Ви можете використовувати PHP backed enumerations як параметри маршруту, бо Symfony автоматично перетворить їх на їхні скалярні значення.

// src/Controller/OrderController.php
namespace App\Controller;

use App\Enum\OrderStatusEnum;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class OrderController extends AbstractController
{
    #[Route('/orders/list/{status}', name: 'list_orders_by_status')]
    public function list(OrderStatusEnum $status = OrderStatusEnum::Paid): Response
    {
        // ...
    }
}

Спеціальні параметри

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

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

_format : Зіставлене значення використовується, щоб задати «формат запиту» обʼєкта Request. Це використовується для таких речей, як встановлення Content-Type відповіді (наприклад, формат json перетворюється на Content-Type application/json).

_fragment : Використовується, щоб задати ідентифікатор фрагмента URL (також званий «hash» або «anchor») — необовʼязкову останню частину URL, що починається з символу # і використовується для ідентифікації частини документа.

_locale : Використовується, щоб задати локаль для запиту.

_query : Масив параметрів рядка запиту, які додаються до згенерованого URL.

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

// src/Controller/ArticleController.php
namespace App\Controller;

// ...
class ArticleController extends AbstractController
{
    #[Route(
        path: '/articles/{_locale}/search.{_format}',
        locale: 'en',
        format: 'html',
        query: ['page' => 1],
        requirements: [
            '_locale' => 'en|fr',
            '_format' => 'html|xml',
        ],
    )]
    public function search(): Response
    {
    }
}
# config/routes.yaml
article_search:
  path:        /articles/{_locale}/search.{_format}
  controller:  App\Controller\ArticleController::search
  locale:      en
  format:      html
  query:
      page:    1
  requirements:
      _locale: en|fr
      _format: html|xml
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\ArticleController;

return Routes::config([
    'article_show' => [
        'path' => '/articles/{_locale}/search.{_format}',
        'controller' => [ArticleController::class, 'search'],
        'locale' => 'en',
        'format' => 'html',
        'query' => ['page' => 1],
        'requirements' => ['_locale' => 'en|fr', '_format' => 'html|xml'],
    ],
]);

Додаткові параметри

В опції defaults маршруту ви можете за бажанням визначити параметри, не включені до конфігурації маршруту. Це корисно, щоб передавати додаткові аргументи контролерам маршрутів:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1, 'title' => 'Hello world!'])]
    public function index(int $page, string $title): Response
    {
        // ...
    }
}
# config/routes.yaml
blog_index:
    path:       /blog/{page}
    controller: App\Controller\BlogController::index
    defaults:
        page: 1
        title: "Hello world!"
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\BlogController;

return Routes::config([
    'blog_index' => [
        'path' => '/blog/{page}',
        'controller' => [BlogController::class, 'index'],
        'defaults' => ['page' => 1, 'title' => 'Hello world!'],
    ],
]);

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

Параметри маршруту можуть містити будь-які значення, крім символу слеша /, бо саме цей символ використовується для розділення різних частин URL. Наприклад, якщо значення token у маршруті /share/{token} містить символ /, цей маршрут не збігатиметься.

Можливе розвʼязання — зробити вимоги до параметра дозвільнішими:

// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class DefaultController extends AbstractController
{
    #[Route('/share/{token}', name: 'share', requirements: ['token' => '.+'])]
    public function share($token): Response
    {
        // ...
    }
}
# config/routes.yaml
share:
    path:       /share/{token}
    controller: App\Controller\DefaultController::share
    requirements:
        token: .+
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\DefaultController;

return Routes::config([
    'share' => [
        'path' => '/share/{token}',
        'controller' => [DefaultController::class, 'share'],
        'requirements' => ['token' => '.+'],
    ],
]);

Примітка. Якщо маршрут визначає кілька параметрів і ви застосовуєте цей дозвільний регулярний вираз до всіх них, ви можете отримати неочікувані результати. Наприклад, якщо визначення маршруту — /share/{path}/{token} і обидва, path і token, приймають /, тоді token отримає лише останню частину, а решту зіставить path.

Примітка. Якщо маршрут містить спеціальний параметр {_format}, вам не слід використовувати вимогу .+ для параметрів, які дозволяють слеші. Наприклад, якщо патерн — /share/{token}.{_format} і {token} дозволяє будь-який символ, URL /share/foo/bar.json вважатиме foo/bar.json токеном, а формат буде порожнім. Це можна розвʼязати, замінивши вимогу .+ на [^.]+, щоб дозволити будь-який символ, крім крапок.

Аліаси маршрутів

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

// src/Controller/ProductController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ProductController
{
    #[Route('/product/{id}', name: 'product_show')]
    public function show(): Response
    {
        // ...
    }
}
# config/routes.yaml
product_show:
    path: /product/{id}
    controller: App\Controller\ProductController::show
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

return Routes::config([
    'product_show' => [
        'path' => '/product/{id}',
        'controller' => [ProductController::class, 'show'],
    ],
]);

Тепер скажімо, ви хочете створити новий маршрут із назвою product_details, який працює точно так само, як product_show.

Замість дублювання оригінального маршруту ви можете створити для нього аліас.

// src/Controller/ProductController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ProductController
{
    // аргумент "alias" призначає цьому маршруту альтернативну назву;
    // аліас вказуватиме на фактичний маршрут "product_show"
    #[Route('/product/{id}', name: 'product_show', alias: ['product_details'])]
    public function show(): Response
    {
        // ...
    }
}
# config/routes.yaml
product_show:
    path: /product/{id}
    controller: App\Controller\ProductController::show

product_details:
    # опція "alias" посилається на назву маршруту, оголошеного вище
    alias: product_show
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;
return Routes::config([
            'product_show' => [
                'path' => '/product/{id}',
                'controller' => [ProductController::class, 'show'],
            ],
            'product_details' => [
                // опція "alias" посилається на імʼя маршруту, оголошеного вище
                'alias' => ['product_show'],
            ],
        ]);

У цьому прикладі в застосунку можна користуватися обома маршрутами, product_show і product_details, і результат буде однаковий.

Формати YAML і PHP - єдиний спосіб задати аліас для маршруту, який вам не належить. З PHP-атрибутами так зробити не вийде.

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

Позначення аліасів маршрутів застарілими

Аліаси маршрутів стають у пригоді, коли треба зберегти зворотну сумісність для перейменованих маршрутів.

Припустімо, ви хочете замінити маршрут product_show на product_details, а старий позначити як застарілий.

У попередньому прикладі аліас product_details вказував на маршрут product_show.

Щоб позначити product_show застарілим, аліас треба «перевернути». Тепер аліасом стає product_show, і він вказуватиме на маршрут product_details. Після цього аліас product_show можна оголосити застарілим.

// src/Controller/ProductController.php
namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\DeprecatedAlias;
use Symfony\Component\Routing\Attribute\Route;

class ProductController
{
    // це виводить таке загальне повідомлення про застарілість:
    // Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
    #[Route('/product/{id}',
        name: 'product_details',
        alias: new DeprecatedAlias(
            aliasName: 'product_show',
            package: 'acme/package',
            version: '1.2',
        ),
    )]
    // Або ж можна задати власне повідомлення про застарілість (доступний плейсхолдер %alias_id%)
    #[Route('/product/{id}',
        name: 'product_details',
        alias: new DeprecatedAlias(
            aliasName: 'product_show',
            package: 'acme/package',
            version: '1.2',
            message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.',
        ),
    )]
    public function show(): Response
    {
        // ...
    }
}
# config/routes.yaml
# Переносимо конкретне визначення маршруту під ``product_details``
product_details:
    path: /product/{id}
    controller: App\Controller\ProductController::show

# Оголошуємо аліас і застарілість у визначенні ``product_show``
product_show:
    alias: product_details

    # це виводить таке загальне повідомлення про застарілість:
    # Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
    deprecated:
        package: 'acme/package'
        version: '1.2'

    # або

    # можна задати власне повідомлення про застарілість (доступний плейсхолдер %alias_id%)
    deprecated:
        package: 'acme/package'
        version: '1.2'
        message: 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.'
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\ProductController;

return Routes::config([
    // Переносимо конкретне визначення маршруту під ``product_details``
    'product_details' => [
        'path' => '/product/{id}',
        'controller' => [ProductController::class, 'show'],
    ],
    // Оголошуємо аліас і застарілість у визначенні ``product_show``
    'product_show' => [
        'alias' => 'product_details',

        // це виводить таке загальне повідомлення про застарілість:
        // Since acme/package 1.2: The "product_show" route alias is deprecated. You should stop using it, as it will be removed in the future.
        'deprecated' => [
            'package' => 'acme/package',
            'version' => '1.2',
        ],

        // або

        // можна задати власне повідомлення про застарілість (доступний плейсхолдер %alias_id%)
        'deprecated' => [
            'package' => 'acme/package',
            'version' => '1.2',
            'message' => 'The "%alias_id%" route alias is deprecated. Please use "product_details" instead.',
        ],
    ],
]);

У цьому прикладі щоразу, коли використовується аліас product_show, спрацьовує попередження про застарілість із порадою перейти на product_details.

Повідомлення насправді є шаблоном: входження плейсхолдера %alias_id% замінюються на імʼя аліаса маршруту. У шаблоні обовʼязково має бути щонайменше одне входження %alias_id%.

Групи маршрутів і префікси

Часто група маршрутів має спільні опції (наприклад, усі маршрути, повʼязані з блогом, починаються з /blog). Тому Symfony має механізм спільної конфігурації маршрутів.

Коли маршрути оголошені атрибутами, спільну конфігурацію задають в атрибуті #[Route] класу контролера. В інших форматах маршрутизації спільну конфігурацію задають опціями під час імпорту маршрутів.

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/blog', requirements: ['_locale' => 'en|es|fr'], name: 'blog_')]
class BlogController extends AbstractController
{
    #[Route('/{_locale}', name: 'index')]
    public function index(): Response
    {
        // ...
    }

    #[Route('/{_locale}/posts/{slug}', name: 'show')]
    public function show(string $slug): Response
    {
        // ...
    }
}
# config/routes.yaml
controllers:
    resource: routing.controllers
    # це додається на початок URL усіх імпортованих маршрутів
    prefix: '/blog'
    # це додається на початок імен усіх імпортованих маршрутів
    name_prefix: 'blog_'
    # ці вимоги додаються до всіх імпортованих маршрутів
    requirements:
        _locale: 'en|es|fr'

    # Імпортований маршрут із порожнім URL перетвориться на "/blog/"
    # Розкоментуйте цю опцію, щоб URL натомість був "/blog"
    # trailing_slash_on_root: false

    # за бажанням можна виключити частину файлів/підтек під час завантаження атрибутів
    # (значення має бути рядком або масивом PHP glob-шаблонів)
    # exclude: '../src/Controller/{Debug*Controller.php}'
// config/routes/attributes.php
namespace Symfony\Component\Routing\Loader\Configurator;

return Routes::config([
    'controllers' => [
        'resource' => '../../src/Controller/',
        'type' => 'attribute',
        // це додається на початок URL усіх імпортованих маршрутів
        'prefix' => '/blog',
        // це додається на початок імен усіх імпортованих маршрутів
        'name_prefix' => 'blog_',
        // ці вимоги додаються до всіх імпортованих маршрутів
        'requirements' => ['_locale' => 'en|es|fr'],

        // Імпортований маршрут із порожнім URL перетвориться на "/blog/"
        // Розкоментуйте цю опцію, щоб URL натомість був "/blog"
        // 'trailing_slash_on_root' => false,

        // за бажанням можна виключити частину файлів/підтек під час завантаження атрибутів
        // (значення має бути рядком або масивом PHP glob-шаблонів)
        // 'exclude' => '../../src/Controller/{Debug*Controller.php}',
    ],
]);

Опція exclude працює лише тоді, коли значення resource є glob-рядком. Якщо ви використовуєте звичайний рядок (наприклад, '../src/Controller'), значення exclude буде проігноровано.

У цьому прикладі маршрут дії index() матиме імʼя blog_index і URL /blog/{_locale}. Маршрут дії show() матиме імʼя blog_show і URL /blog/{_locale}/posts/{slug}. Обидва маршрути також перевірятимуть, що параметр _locale відповідає регулярному виразу, заданому в атрибуті класу.

Якщо якийсь із префіксованих маршрутів має порожній шлях, Symfony додає до нього кінцевий слеш. У попередньому прикладі порожній шлях із префіксом /blog дасть URL /blog/. Щоб уникнути такої поведінки, встановіть опцію trailing_slash_on_root у false (з PHP-атрибутами ця опція недоступна):

# config/routes.yaml
controllers:
    resource: routing.controllers
    prefix:   '/blog'
    trailing_slash_on_root: false
    # ...
// config/routes/attributes.php
namespace Symfony\Component\Routing\Loader\Configurator;

return Routes::config([
    'controllers' => [
        'resource' => '../../src/Controller/',
        'type' => 'attribute',
        'prefix' => '/blog',
        'trailing_slash_on_root' => false,
    ],
]);

// ця опція також доступна під час префіксування колекцій маршрутів:

// config/routes.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;

return function (RoutingConfigurator $routes) {
    $routes->collection('...')
        ->prefix('/categories', trailingSlashOnRoot: false)
        // ...
    ;
};

Нове у версії 8.1

Аргумент trailingSlashOnRoot методу CollectionConfigurator::prefix() зʼявився в Symfony 8.1.

Див. також

Symfony вміє імпортувати маршрути з різних джерел (/routing/custom_route_loader), і ви навіть можете написати власний завантажувач маршрутів.

Отримання імені маршруту та параметрів

Обʼєкт Request, створений Symfony, зберігає всю конфігурацію маршруту (зокрема імʼя та параметри) в «атрибутах запиту». Отримати цю інформацію в контролері можна через обʼєкт Request:

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class BlogController extends AbstractController
{
    #[Route('/blog', name: 'blog_list')]
    public function list(Request $request): Response
    {
        $routeName = $request->attributes->get('_route');
        $routeParameters = $request->attributes->get('_route_params');

        // так можна отримати всі доступні атрибути (не тільки повʼязані з маршрутизацією):
        $allAttributes = $request->attributes->all();

        // ...
    }
}

У сервісах цю інформацію отримують через впровадження сервісу RequestStack. У шаблонах використовуйте глобальну змінну Twig app, щоб дістати імʼя поточного маршруту (app.current_route) та його параметри (app.current_route_parameters).

Спеціальні маршрути

Symfony має кілька спеціальних контролерів, які рендерять шаблони й перенаправляють на інші маршрути прямо з конфігурації маршруту, тож писати для цього дію контролера не потрібно.

Рендеринг шаблону безпосередньо з маршруту

Прочитайте розділ про рендеринг шаблону з маршруту в основній статті про шаблони Symfony.

Перенаправлення на URL і маршрути безпосередньо з маршруту

Використовуйте RedirectController, щоб перенаправляти на інші маршрути та URL:

# config/routes.yaml
doc_shortcut:
    path: /doc
    controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
    defaults:
        route: 'doc_page'
        # за бажанням можна задати аргументи, які передаються в маршрут
        page: 'index'
        version: 'current'
        # перенаправлення за замовчуванням тимчасові (код 302), але їх можна зробити постійними (код 301)
        permanent: true
        # додайте це, щоб зберегти початкові параметри рядка запиту під час перенаправлення
        keepQueryParams: true
        # додайте це, щоб зберегти HTTP-метод під час перенаправлення. Статус перенаправлення змінюється
        # * для тимчасових перенаправлень використовується код 307 замість 302
        # * для постійних перенаправлень використовується код 308 замість 301
        keepRequestMethod: true
        # додайте це, щоб під час перенаправлення прибрати всі атрибути початкового маршруту
        ignoreAttributes: true
        # або вкажіть, які саме атрибути ігнорувати:
        # ignoreAttributes: ['offset', 'limit']

legacy_doc:
    path: /legacy/doc
    controller: Symfony\Bundle\FrameworkBundle\Controller\RedirectController
    defaults:
        # це значення може бути абсолютним шляхом або абсолютним URL
        path: 'https://legacy.example.com/doc'
        permanent: true
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;

return Routes::config([
    'doc_shortcut' => [
        'path' => '/doc',
        'controller' => [RedirectController::class, 'doc_page'],
        'defaults' => [
            'route' => 'doc_page',
            // за бажанням можна задати аргументи, які передаються в маршрут
            'page' => 'index',
            'version' => 'current',
            // перенаправлення за замовчуванням тимчасові (код 302), але їх можна зробити постійними (код 301)
            'permanent' => true,
            // додайте це, щоб зберегти початкові параметри рядка запиту під час перенаправлення
            'keepQueryParams' => true,
            // додайте це, щоб зберегти HTTP-метод під час перенаправлення. Статус перенаправлення змінюється
            // * для тимчасових перенаправлень використовується код 307 замість 302
            // * для постійних перенаправлень використовується код 308 замість 301
            'keepRequestMethod' => true,
            // додайте це, щоб під час перенаправлення прибрати всі атрибути початкового маршруту
            'ignoreAttributes' => true,
            // або вкажіть, які саме атрибути ігнорувати:
            // 'ignoreAttributes' => ['offset', 'limit'],
        ],
    ],
    'legacy_doc' => [
        'path' => '/legacy/doc',
        'controller' => [RedirectController::class, 'legacy_doc'],
        'defaults' => [
            // це значення може бути абсолютним шляхом або абсолютним URL
            'path' => 'https://legacy.example.com/doc',
            'permanent' => true,
        ],
    ],
]);

Symfony також надає засоби для перенаправлення всередині контролерів.

Перенаправлення URL із кінцевим слешем

Історично URL дотримувалися UNIX-угоди: кінцевий слеш для тек (наприклад, https://example.com/foo/) і без нього для файлів (https://example.com/foo). Віддавати різний вміст для обох URL цілком припустимо, але тепер прийнято вважати їх одним і тим самим URL та перенаправляти між ними.

Symfony дотримується цієї логіки під час перенаправлення між URL з кінцевим слешем і без нього (але тільки для запитів GET і HEAD):

URL маршруту Якщо запитаний URL - /foo Якщо запитаний URL - /foo/
/foo Збіг (відповідь зі статусом 200) Перенаправлення 301 на /foo
/foo/ Перенаправлення 301 на /foo/ Збіг (відповідь зі статусом 200)

Маршрутизація за піддоменом

Маршрути можуть мати опцію host, яка вимагає, щоб HTTP-хост вхідного запиту збігався з певним значенням. У прикладі нижче обидва маршрути відповідають одному шляху (/), але один із них відповідає лише на конкретне імʼя хоста:

// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class MainController extends AbstractController
{
    #[Route('/', name: 'mobile_homepage', host: 'm.example.com')]
    public function mobileHomepage(): Response
    {
        // ...
    }

    #[Route('/', name: 'homepage')]
    public function homepage(): Response
    {
        // ...
    }
}
# config/routes.yaml
mobile_homepage:
    path:       /
    host:       m.example.com
    controller: App\Controller\MainController::mobileHomepage

homepage:
    path:       /
    controller: App\Controller\MainController::homepage
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\MainController;

return Routes::config([
    'mobile_homepage' => [
        'path' => '/',
        'host' => 'm.example.com',
        'controller' => [MainController::class, 'mobileHomepage'],
    ],
    'homepage' => [
        'path' => '/',
        'controller' => [MainController::class, 'homepage'],
    ],
]);

Значення опції host може містити параметри (це зручно в мультитенантних застосунках), і ці параметри теж можна перевіряти через requirements:

// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class MainController extends AbstractController
{
    #[Route(
        '/',
        name: 'mobile_homepage',
        host: '{subdomain}.example.com',
        defaults: ['subdomain' => 'm'],
        requirements: ['subdomain' => 'm|mobile'],
    )]
    public function mobileHomepage(): Response
    {
        // ...
    }

    #[Route('/', name: 'homepage')]
    public function homepage(): Response
    {
        // ...
    }
}
# config/routes.yaml
mobile_homepage:
    path:       /
    host:       "{subdomain}.example.com"
    controller: App\Controller\MainController::mobileHomepage
    defaults:
        subdomain: m
    requirements:
        subdomain: m|mobile

homepage:
    path:       /
    controller: App\Controller\MainController::homepage
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\MainController;

return Routes::config([
    'mobile_homepage' => [
        'path' => '/',
        'host' => '{subdomain}.example.com',
        'controller' => [MainController::class, 'mobileHomepage'],
        'defaults' => ['subdomain' => 'm'],
        'requirements' => ['subdomain' => 'm|mobile'],
    ],
    'homepage' => [
        'path' => '/',
        'controller' => [MainController::class, 'homepage'],
    ],
]);

У прикладі вище параметр subdomain має значення за замовчуванням, інакше довелося б передавати значення піддомену щоразу під час генерації URL для цих маршрутів.

Опцію host можна задати й під час імпорту маршрутів, щоб усі вони вимагали це імʼя хоста.

Використовуючи маршрутизацію за піддоменом, у функціональних тестах треба виставляти HTTP-заголовок Host, інакше маршрути не збігатимуться:

$crawler = $client->request(
    'GET',
    '/',
    [],
    [],
    ['HTTP_HOST' => 'm.example.com']
    // або взяти значення з якогось параметра конфігурації:
    // ['HTTP_HOST' => 'm.'.$client->getContainer()->getParameter('domain')]
);

В опції host можна також використовувати інлайновий формат значень за замовчуванням і вимог: {subdomain<m|mobile>?m}.example.com

Локалізовані маршрути (i18n)

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

// src/Controller/CompanyController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class CompanyController extends AbstractController
{
    #[Route(path: [
        'en' => '/about-us',
        'nl' => '/over-ons'
        // за бажанням можна задати шлях без локалі. Він використовуватиметься
        // для будь-якої локалі, що не збігається з наведеними вище
        '/about-us',
    ], name: 'about_us')]
    public function about(): Response
    {
        // ...
    }
}
# config/routes.yaml
about_us:
    path:
        en: /about-us
        nl: /over-ons
    controller: App\Controller\CompanyController::about
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\CompanyController;

return Routes::config([
    'about_us' => [
        'path' => [
            'en' => '/about-us',
            'nl' => '/over-ons',
            // за бажанням можна задати шлях без локалі. Він використовуватиметься
            // для будь-якої локалі, що не збігається з наведеними вище
            '/about-us',
        ],
        'controller' => [CompanyController::class, 'about'],
    ],
]);

З PHP-атрибутами для локалізованих маршрутів масив шляхів треба передавати через іменований параметр path.

Коли локалізований маршрут збігається, Symfony автоматично використовує ту саму локаль протягом усього запиту.

Якщо застосунок працює з повними локалями «мова + територія» (наприклад, fr_FR, fr_BE), а URL для споріднених локалей однакові, у маршрутах можна вказати лише мовну частину (наприклад, fr), щоб не повторювати ті самі URL.

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

# config/routes.yaml
controllers:
    prefix:
        en: '' # не додавати префікс до URL для англійської, типової локалі
        nl: '/nl'
    resource: routing.controllers
// config/routes/attributes.php
namespace Symfony\Component\Routing\Loader\Configurator;

return Routes::config([
    'controllers' => [
        'resource' => '../../src/Controller/',
        'type' => 'attribute',
        'prefix' => [
            'en' => '', // не додавати префікс до URL для англійської, типової локалі
            'nl' => '/nl',
        ],
    ],
]);

Якщо маршрут, який імпортується, містить у власному визначенні спеціальний параметр _locale, Symfony імпортує його лише для цієї локалі, а не для інших налаштованих префіксів локалей.

Наприклад, якщо маршрут містить у визначенні locale: 'en' і імпортується з локалями en (префікс порожній) та nl (префікс /nl), він буде доступний тільки в локалі en, але не в nl.

Ще одна типова вимога - розміщувати сайт на різних доменах залежно від локалі. Для цього задають окремий хост для кожної локалі.

# config/routes.yaml
controllers:
    resource: routing.controllers
    host:
        en: 'www.example.com'
        nl: 'www.example.nl'
// config/routes/attributes.php
namespace Symfony\Component\Routing\Loader\Configurator;

return Routes::config([
    'controllers' => [
        'resource' => '../../src/Controller/',
        'type' => 'attribute',
        'host' => [
            'en' => 'www.example.com',
            'nl' => 'www.example.nl',
        ],
    ],
]);

Stateless-маршрути

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

Подробиці див. у розділі /http_cache.

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

// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;

class MainController extends AbstractController
{
    #[Route('/', name: 'homepage', stateless: true)]
    public function homepage(): Response
    {
        // ...
    }
}
# config/routes.yaml
homepage:
    controller: App\Controller\MainController::homepage
    path: /
    stateless: true
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\MainController;

return Routes::config([
    'homepage' => [
        'controller' => [MainController::class, 'homepage'],
        'path' => '/',
        'stateless' => true,
    ],
]);

Тепер, якщо сесія все ж використовується, застосунок повідомить про це залежно від параметра kernel.debug:

  • enabled: буде викинуто виняток Symfony\Component\HttpKernel\Exception\UnexpectedSessionUsageException
  • disabled: буде записано попередження в лог

Це допоможе зрозуміти й виправити неочікувану поведінку застосунку.

Генерація URL

Системи маршрутизації працюють у двох напрямках:

  1. вони повʼязують URL із контролерами (як описано в попередніх розділах);
  2. вони генерують URL для заданого маршруту.

Генерація URL із маршрутів позбавляє потреби вручну писати значення <a href="..."> у HTML-шаблонах. Крім того, якщо URL якогось маршруту зміниться, достатньо оновити конфігурацію маршруту - і всі посилання оновляться.

Щоб згенерувати URL, потрібно вказати імʼя маршруту (наприклад, blog_show) та значення параметрів, визначених маршрутом (наприклад, slug = my-blog-post).

Саме тому кожен маршрут має внутрішнє імʼя, унікальне в межах застосунку. Якщо ви не задасте імʼя маршруту явно через опцію name, Symfony згенерує його автоматично на основі контролера та дії.

Symfony оголошує аліаси маршрутів на основі FQCN, якщо цільовий клас має метод __invoke(), який додає маршрут, і якщо цільовий клас додав рівно один маршрут. Symfony також автоматично додає аліас для кожного методу, який визначає лише один маршрут. Розгляньмо такий клас:

// src/Controller/MainController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Attribute\Route;

final class MainController extends AbstractController
{
    #[Route('/', name: 'homepage')]
    public function homepage(): Response
    {
        // ...
    }
}

Symfony додасть аліас маршруту з іменем App\Controller\MainController::homepage.

Генерація URL у контролерах

Якщо ваш контролер успадковує AbstractController, використовуйте хелпер generateUrl():

// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class BlogController extends AbstractController
{
    #[Route('/blog', name: 'blog_list')]
    public function list(): Response
    {
        // генеруємо URL без аргументів маршруту
        $signUpPage = $this->generateUrl('sign_up');

        // генеруємо URL з аргументами маршруту
        $userProfilePage = $this->generateUrl('user_profile', [
            'username' => $user->getUserIdentifier(),
        ]);

        // за замовчуванням згенеровані URL є "абсолютними шляхами". Передайте третій
        // необовʼязковий аргумент, щоб згенерувати інші URL (наприклад, "абсолютний URL")
        $signUpPage = $this->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);

        // коли маршрут локалізований, Symfony за замовчуванням бере локаль поточного запиту
        // передайте інше значення '_locale', якщо хочете задати локаль явно
        $signUpPageInDutch = $this->generateUrl('sign_up', ['_locale' => 'nl']);

        // ...
    }
}

Якщо передати в метод generateUrl() параметри, яких немає у визначенні маршруту, вони потраплять у згенерований URL як рядок запиту:

$this->generateUrl('blog', ['page' => 2, 'category' => 'Symfony']);
// маршрут 'blog' визначає лише параметр 'page'; згенерований URL буде таким:
// /blog/2?category=Symfony

Обʼєкти перетворюються на рядок, коли використовуються як плейсхолдери, але не перетворюються, коли передаються як додаткові параметри. Тож якщо ви передаєте обʼєкт (наприклад, Uuid) як значення додаткового параметра, його треба явно привести до рядка:

$this->generateUrl('blog', ['uuid' => (string) $entity->getUuid()]);

Якщо ваш контролер не успадковує AbstractController, вам потрібно отримати сервіси у контролері й далі діяти за інструкціями наступного розділу.

Генерація URL у сервісах

Впровадьте Symfony-сервіс router у власні сервіси й використовуйте його метод generate(). З автоматичним звʼязуванням сервісів (autowiring) достатньо додати аргумент у конструктор сервісу й зазначити для нього тип Symfony\Component\Routing\Generator\UrlGeneratorInterface:

// src/Service/SomeService.php
namespace App\Service;

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class SomeService
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
    ) {
    }

    public function someMethod(): void
    {
        // ...

        // генеруємо URL без аргументів маршруту
        $signUpPage = $this->urlGenerator->generate('sign_up');

        // генеруємо URL з аргументами маршруту
        $userProfilePage = $this->urlGenerator->generate('user_profile', [
            'username' => $user->getUserIdentifier(),
        ]);

        // за замовчуванням згенеровані URL є "абсолютними шляхами". Передайте третій
        // необовʼязковий аргумент, щоб згенерувати інші URL (наприклад, "абсолютний URL")
        $signUpPage = $this->urlGenerator->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);

        // коли маршрут локалізований, Symfony за замовчуванням бере локаль поточного запиту
        // передайте інше значення '_locale', якщо хочете задати локаль явно
        $signUpPageInDutch = $this->urlGenerator->generate('sign_up', ['_locale' => 'nl']);
    }
}

Генерація URL у шаблонах

Прочитайте розділ про створення посилань між сторінками в основній статті про шаблони Symfony.

Генерація URL у JavaScript

Якщо ваш JavaScript-код вбудований у Twig-шаблон, можна скористатися Twig-функціями path() та url(), щоб згенерувати URL і зберегти їх у JavaScript-змінних. Фільтр escape() потрібен, щоб екранувати значення, небезпечні для JavaScript:

<script>
    const route = "{{ path('blog_show', {slug: 'my-blog-post'})|escape('js') }}";
</script>

Якщо URL треба генерувати динамічно або якщо ви пишете чистий JavaScript, це рішення не працює. У таких випадках погляньте на FOSJsRoutingBundle.

Генерація URL у командах

Генерація URL у командах працює так само, як генерація URL у сервісах. Різниця лише в тому, що команди виконуються поза HTTP-контекстом. Тому, генеруючи абсолютні URL, ви отримаєте імʼя хоста http://localhost/ замість реального.

Розвʼязання - налаштувати опцію default_uri, яка задає «контекст запиту» для команд під час генерації URL:

# config/packages/routing.yaml
framework:
    router:
        # ...
        default_uri: 'https://example.org/my/path/'
// config/packages/routing.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'router' => [
            'default_uri' => 'https://example.org/my/path/',
        ],
    ],
]);

Тепер генерація URL у командах даватиме очікуваний результат:

// src/Command/MyCommand.php
namespace App\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
// ...

#[AsCommand(name: 'app:my-command')]
class MyCommand
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
    ) {
    }

    public function __invoke(SymfonyStyle $io): int
    {
        // генеруємо URL без аргументів маршруту
        $signUpPage = $this->urlGenerator->generate('sign_up');

        // генеруємо URL з аргументами маршруту
        $userProfilePage = $this->urlGenerator->generate('user_profile', [
            'username' => $user->getUserIdentifier(),
        ]);

        // за замовчуванням згенеровані URL є "абсолютними шляхами". Передайте третій
        // необовʼязковий аргумент, щоб згенерувати інші URI (наприклад, "абсолютний URL")
        $signUpPage = $this->urlGenerator->generate('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);

        // коли маршрут локалізований, Symfony за замовчуванням бере локаль поточного запиту
        // передайте інше значення '_locale', якщо хочете задати локаль явно
        $signUpPageInDutch = $this->urlGenerator->generate('sign_up', ['_locale' => 'nl']);

        // ...
    }
}

За замовчуванням URL, згенеровані для вебресурсів (assets), використовують те саме значення default_uri, але його можна змінити параметрами контейнера asset.request_context.base_path та asset.request_context.secure.

За замовчуванням маршрути, згенеровані поза HTTP-контекстом, використовують типову локаль як значення параметра _locale. Це можна перевизначити, передавши інше значення параметра _locale під час генерації кожного маршруту.

Перевірка існування маршруту

У дуже динамічних застосунках буває потрібно перевірити, чи маршрут існує, перш ніж генерувати за ним URL. У таких випадках не використовуйте метод Symfony\Component\Routing\Router::getRouteCollection, бо він перебудовує кеш маршрутизації й уповільнює застосунок.

Натомість спробуйте згенерувати URL і перехопіть виняток Symfony\Component\Routing\Exception\RouteNotFoundException, який викидається, коли маршруту не існує:

use Symfony\Component\Routing\Exception\RouteNotFoundException;

// ...

try {
    $url = $this->router->generate($routeName, $routeParameters);
} catch (RouteNotFoundException $e) {
    // маршрут не визначено...
}

Примусовий HTTPS у згенерованих URL

Якщо ваш сервер працює за проксі, який термінує SSL, обовʼязково налаштуйте Symfony для роботи за проксі.

Конфігурація схеми використовується тільки для не-HTTP запитів. Опція schemes разом із неправильним налаштуванням проксі призведе до циклу перенаправлень.

За замовчуванням згенеровані URL використовують ту саму HTTP-схему, що й поточний запит. У консольних командах, де HTTP-запиту немає, URL за замовчуванням використовують http. Це можна змінити окремо для команди (через метод роутера getContext()) або глобально опцією default_uri:

# config/packages/routing.yaml
framework:
    router:
        default_uri: 'https://example.org'
// config/packages/routing.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

return App::config([
    'framework' => [
        'router' => [
            'default_uri' => 'https://example.org',
        ],
    ],
]);

Застаріло у версії 8.1

Параметри контейнера router.request_context.scheme та router.request_context.host оголошені застарілими в Symfony 8.1. Використовуйте натомість опцію framework.router.default_uri.

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

// src/Controller/SecurityController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class SecurityController extends AbstractController
{
    #[Route('/login', name: 'login', schemes: ['https'])]
    public function login(): Response
    {
        // ...
    }
}
# config/routes.yaml
login:
    path:       /login
    controller: App\Controller\SecurityController::login
    schemes:    [https]
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;

use App\Controller\SecurityController;

return Routes::config([
    'login' => [
        'path' => '/login',
        'controller' => [SecurityController::class, 'login'],
        'schemes' => ['https'],
    ],
]);

URL, згенерований для маршруту login, завжди використовуватиме HTTPS. Тобто під час генерації URL Twig-функцією path() ви можете отримати абсолютний URL замість відносного, якщо HTTP-схема початкового запиту відрізняється від схеми маршруту:

{# якщо поточна схема - HTTPS, генерується відносний URL: /login #}
{{ path('login') }}

{# якщо поточна схема - HTTP, генерується абсолютний URL, щоб змінити
   схему: https://example.com/login #}
{{ path('login') }}

Вимога щодо схеми діє й для вхідних запитів. Якщо спробувати відкрити URL /login через HTTP, вас автоматично перенаправить на той самий URL, але зі схемою HTTPS.

Щоб змусити групу маршрутів працювати через HTTPS, можна задати типову схему під час їх імпорту. Наступний приклад примусово вмикає HTTPS для всіх маршрутів, оголошених атрибутами:

# config/routes.yaml
controllers:
    schemes: [https]
    resource: routing.controllers
// config/routes/attributes.php
namespace Symfony\Component\Routing\Loader\Configurator;

return Routes::config([
    'controllers' => [
        'resource' => '../../src/Controller/',
        'type' => 'attribute',
        'schemes' => ['https'],
    ],
]);

Компонент Security дає ще один спосіб примусово вмикати HTTP або HTTPS - через налаштування requires_channel.

Підписування URI

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

Створення й перевірка підписаних URI

Symfony має інструмент для підписування URI - сервіс Symfony\Component\HttpFoundation\UriSigner, який можна впровадити у свої сервіси або контролери:

// src/Service/SomeService.php
namespace App\Service;

use Symfony\Component\HttpFoundation\UriSigner;

class SomeService
{
    public function __construct(
        private UriSigner $uriSigner,
    ) {
    }

    public function someMethod(): void
    {
        // ...

        // згенеруйте URL самостійно або отримайте його якось інакше...
        $url = 'https://example.com/foo/bar?sort=desc';

        // підписуємо URL (додається параметр запиту '_hash')
        $signedUrl = $this->uriSigner->sign($url);
        // $url = 'https://example.com/foo/bar?sort=desc&_hash=e4a21b9'
    }
}

Щоб перевірити підписаний URI, скористайтеся check():

$this->uriSigner->check($signedUrl);

Якщо у вас уже є поточний обʼєкт Request, замість цього можна використати checkRequest():

$this->uriSigner->checkRequest($request);

Використовуйте метод verify() замість check(), якщо вам також потрібні причини, чому підпис недійсний.

Термін дії підписаних URI

З міркувань безпеки підписані URI часто роблять такими, що втрачають чинність через певний час (наприклад, коли їх використовують для скидання облікових даних користувача). Типово підписані URI не мають терміну дії, але його можна задати аргументом $expiration методу Symfony\Component\HttpFoundation\UriSigner::sign:

// підписуємо URL із явною датою завершення дії
$signedUrl = $this->uriSigner->sign($url, new \DateTimeImmutable('2050-01-01'));
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=2524608000&_hash=e4a21b9'

Також можна передати DateInterval або Unix-мітку часу:

$signedUrl = $this->uriSigner->sign($url, new \DateInterval('PT10S'));  // дійсний 10 секунд від цього моменту
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=1712414278&_hash=e4a21b9'

$signedUrl = $this->uriSigner->sign($url, 4070908800); // мітка часу для дати 2099-01-01
// $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=4070908800&_hash=e4a21b9'

Дата й час завершення дії додаються до підписаного URI як мітка часу в параметрі запиту _expiration.

Перевірка підписаних URI

Коли потрібно знати не просто факт недійсності підписаного URI, а її причину, скористайтеся методом verify(), який у разі невдачі кидає винятки:

use Symfony\Component\HttpFoundation\Exception\ExpiredSignedUriException;
use Symfony\Component\HttpFoundation\Exception\UnsignedUriException;
use Symfony\Component\HttpFoundation\Exception\UnverifiedSignedUriException;

try {
    $uriSigner->verify($uri); // $uri може бути рядком або обʼєктом Request
    // ...
} catch (UnsignedUriException) {
    // URI не підписаний
} catch (UnverifiedSignedUriException) {
    // URI підписаний, але підпис недійсний
} catch (ExpiredSignedUriException) {
    // URI підписаний, але термін його дії минув
}

Якщо встановлено symfony/clock, він використовується під час генерації та перевірки дат завершення дії. Це дозволяє підміняти поточний час у тестах.

Атрибути контролера для перевірки підписаних URI

Замість того щоб вручну викликати check() чи verify() на підписувачі URI, вхідні запити можна перевіряти декларативно, атрибутом #[IsSignatureValid]:

// src/Controller/SomeController.php
// ...

use Symfony\Component\HttpKernel\Attribute\IsSignatureValid;

#[IsSignatureValid]
public function someAction(): Response
{
    // якщо запит не підписаний, має недійсний підпис
    // або термін його дії минув, кидається SignedUriException
    // ...
}

Типово перевіряється кожен запит. Опція methods обмежує перевірку конкретними HTTP-методами:

// перевіряти лише POST-запити
#[IsSignatureValid(methods: 'POST')]
public function createItem(): Response

// перевіряти і POST-, і PUT-запити
#[IsSignatureValid(methods: ['POST', 'PUT'])]
public function updateItem(): Response

#[IsSignatureValid] можна застосувати й на рівні класу контролера, щоб перевіряти підписи URI для всіх його дій:

// src/Controller/SecureController.php
// ...

#[IsSignatureValid]
class SecureController extends AbstractController
{
    // ...
}

Розвʼязання проблем

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

Controller "App\Controller\BlogController::show()" requires that you
provide a value for the "$slug" argument.

Таке буває, коли метод вашого контролера має аргумент (наприклад, $slug):

public function show(string $slug): Response
{
    // ...
}

але в шляху маршруту немає параметра {slug} (шлях виглядає як /blog/show). Додайте {slug} до шляху маршруту: /blog/show/{slug} - або задайте аргументу значення за замовчуванням (тобто $slug = null).

Some mandatory parameters are missing ("slug") to generate a URL for route
"blog_show".

Це означає, що ви намагаєтесь згенерувати URL для маршруту blog_show, але не передаєте значення slug (воно обовʼязкове, бо в шляху маршруту є параметр {slug}). Щоб виправити це, передайте значення slug під час генерації маршруту:

$this->generateUrl('blog_show', ['slug' => 'slug-value']);

або, у Twig:

{{ path('blog_show', {slug: 'slug-value'}) }}

Дізнатися більше про маршрутизацію

routing/*
Стан перекладу

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

46%
Готовності
2
У роботі
14
Ще не перекладено
Глосарій термінів