Routing

Дата оновлення перекладу 2024-06-10

Routing

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
# config/routes/attributes.yaml
controllers:
    resource:
        path: ../../src/Controller/
        namespace: App\Controller
    type: attribute

kernel:
    resource: App\Kernel
    type: attribute

Ця конфігурація повідомляє Symfony шукати маршрути, визначені як атрибути у класах, оголошених у просторі імен App\Controller та збережених у каталозі src/Controller/, який дотримується стандарту PSR-4. Ядро також може діяти як контролер, що особливо корисно для маленьких додатків, які використовують Symfony в якості мікрофреймворку.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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.

Note

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

Caution

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

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

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

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

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

1
2
3
4
5
6
7
8
9
# config/routes.yaml
blog_list:
    path: /blog
    # значення контролера має формат 'controller_class::method_name'
    controller: App\Controller\BlogController::list

    # якщо дія реалізується як метод __invoke() класу контролера,
    # ви можете пропустити частину '::method_name':
    # controller: App\Controller\BlogController

Note

За замовчуванням, Symfony завантажує лише маршрути, визначені у форматі YAML. Якщо ви визначаєте маршрути у форматах XML та/або PHP, оновіть файл src/Kernel.php, щоб додати підтримку для розширень файлів .xml та .php.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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
    {
        // ... відредагувати пост
    }
}

Tip

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

Вирази, що збігаються

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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',
        // expressions can retrieve route parameter values using the "params" variable
        condition: "params['id'] < 1000"
    )]
    public function showPost(int $id): Response
    {
        // ... return a JSON response with the post
    }
}

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

context
Екземпляр RequestContext, який містить найбільш фундаментальну інформацію про маршрут, що співставляється.
request
Об'єкт запиту Symfony , який представляє поточний запит.
params
Масив співставлених параметрів маршруту для поточного маршруту.

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

env(string $name)
Повертає значення змінної, використовуючи процесори змінних середовища
service(string $alias)

Повертає сервіс умов маршрутизації.

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

1
2
3
4
5
6
7
8
9
10
11
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(), щоб послатися на цей сервіс всередині умов:

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

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

Caution

Умови не беруться до уваги при генеруванні URL (що пояснюється пізніше у цій статті).

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

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

1
2
3
4
5
6
7
8
9
10
11
12
$ php bin/console debug:router

----------------  -------  -------  -----  --------------------------------------------
Ім'я              Метод    Схема    Хост   Шлях
----------------  -------  -------  -----  --------------------------------------------
homepage          ANY      ANY      ANY    /
contact           GET      ANY      ANY    /contact
contact_process   POST     ANY      ANY    /contact
article_show      ANY      ANY      ANY    /articles/{_locale}/{year}/{title}.{_format}
blog              ANY      ANY      ANY    /blog/{page}
blog_show         ANY      ANY      ANY    /blog/{slug}
----------------  -------  -------  -----  --------------------------------------------

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

1
2
3
4
5
6
7
8
9
10
11
$ php bin/console debug:router app_lucky_number

+-------------+---------------------------------------------------------+
| Властивість | Значення                                                |
+-------------+---------------------------------------------------------+
| Ім'я шляху  | app_lucky_number                                        |
| Шлях        | /lucky/number/{max}                                     |
| ...         | ...                                                     |
| Опції       | compiler_class: Symfony\Component\Routing\RouteCompiler |
|             | utf8: true                                              |
+-------------+---------------------------------------------------------+

Tip

Скористайтеся опцією --show-aliases, щоб показати всі доступні псевдоніми для заданого маршруту.

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

1
2
3
$ 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}:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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'

        // ...
    }
}

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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' => '\d+'])]
    public function list(int $page): Response
    {
        // ...
    }

    #[Route('/blog/{slug}', name: 'blog_show')]
    public function show($slug): Response
    {
        // ...
    }
}

Опція requirements визначає регулярні PHP-вирази, яким мають відповідати параметри маршруту для того, щоб співпадав увесь маршрут. В цьому прикладі, \d+ - це регулярний вираз, який співпадає з однозначним числом будь-якої довжини. Тепер:

URL ??????? ?????????
/blog/2 blog_list $page = 2
/blog/my-first-post blog_show $slug = my-first-post

Tip

Зчислення Requirement містить колекцію часто використовуваних констант регулярних виразів, таких як цифри, дати та UUID, які можуть бути використані як вимоги параметрів маршруту.

Tip

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

Tip

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

Note

При використанні регулярних виразів у параметрах маршруту, ви можете встановити опцію маршруту utf8 як true, щоб зробити так, щоб будь-який символ . співпадав з будь-яким символом UTF-8, а не тільки з одним бітом.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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<\d+>}', name: 'blog_list')]
    public function list(int $page): Response
    {
        // ...
    }
}

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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' => '\d+'])]
    public function list(int $page = 1): Response
    {
        // ...
    }
}

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

Caution

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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<\d+>?1}', name: 'blog_list')]
    public function list(int $page): Response
    {
        // ...
    }
}

Tip

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

Параметр пріоритетності

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// src/Controller/BlogController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
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
    {
        // ...
    }
}

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

Конверсія параметрів

Розповсюдженої потребою маршрутизації є конверсія значення, яке зберігається в деякому параметрі (наприклад, ціле число, діюче в якості ID користувача), у інше значення (наприклад, об'єкт, репрезентуючий користувача). Ця функція називається "param converter".

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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}', name: 'blog_show')]
    public function show(BlogPost $post): Response
    {
        // $post це об'єкт, чий слаг відповідає параметру маршрутизації

        // ...
    }
}

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

Перегляньте :ref:`Документацію з перетворення параметрів Doctrine <doctrine-entity-value-resolver>', щоб дізнатися про атрибут #[MapEntity], який можна використовувати для налаштування запити до бази даних, які використовуються для отримання об'єкта з параметра маршруту.

Параметри підтримуваних зчислень

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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 format" об'єкта Request. Це використовується для таких речей, як установка Content-Type відповіді (наприклад, формат json переводиться у Content-Type для application/json).
_fragment
Використовується для установки ідентификатора фрагмента, що є останньою необов'язковою частиною URL, яка починається з символу # і використовується для ідентифікації частини документа.
_locale
Використовується для установки локалі у запиті.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Controller/ArticleController.php
namespace App\Controller;

// ...
class ArticleController extends AbstractController
{
    #[Route(
        path: '/articles/{_locale}/search.{_format}',
        locale: 'en',
        format: 'html',
        requirements: [
            '_locale' => 'en|fr',
            '_format' => 'html|xml',
        ],
    )]
    public function search(): Response
    {
    }
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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
    {
        // ...
    }
}

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

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

Можливим вирішенням буде зміна вимог маршруту, щоб вони були більш вільними:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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
    {
        // ...
    }
}

Note

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

Note

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

Псевдоніми маршрутів

Псевдонім маршуту дозволяє вам мати багато імен для одного маршруту:

1
2
3
# config/routes.yaml
new_route_name:
    alias: original_route_name

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

Оголошення застарівання псевдонімів маршрутів

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new_route_name:
    alias: original_route_name

    # це виводить наступне загальне повідомлення старіння:
    # Починаючи з acme/package 1.2: Псевдонім маршруту "new_route_name" застарів. Ви маєте перестати його використовувати, так як він буде видалений у майбутньому.
    deprecated:
        package: 'acme/package'
        version: '1.2'

    # ви також можете визначити користувацьке повідомлення про старіння (доступний заповнювач %alias_id%)
    deprecated:
        package: 'acme/package'
        version: '1.2'
        message: 'The "%alias_id%" route alias is deprecated. Do not use it anymore.'

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

Повідомлення насправді є шаблоном повідомлення, яке заміняє випадки використання заповнювача %alias_id% імʼям псевдоніма маршруту. Ви повинні мати хоча б один випадок використання заповнювача %alias_id% у вашому шаблоні.

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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
    {
        // ...
    }
}

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

Note

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

1
2
3
4
5
6
7
# config/routes/attributes.yaml
controllers:
    resource: '../../src/Controller/'
    type:     attribute
    prefix:   '/blog'
    trailing_slash_on_root: false
    # ...

See also

Symfony може імпортувати маршрути з інших джерел і ви можете навіть створювати власний завантажувач маршрутів.

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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();

        // ...
    }
}

Ви також можете отримати цю інформацію в сервісах, впроваджуючи сервіс request_stack, щоб отримати об'єкт Запиту в сервісі. У шаблонах, використовуйте глобальну змінну додатку Twig , щоб отримати запит та його атрибути:

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

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

Відображення шаблону прямо з маршруту

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 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
        # * для постійних перенаправлень використовується статус-код 307 замість 301
        keepRequestMethod: true

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

Tip

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-хост вхідних запитів співападав з деяким конкретним значенням. У наступному прикладі обидва маршрути співпадають з одним шляхом (/), але один з них відповідає лише на певне ім'я хосту:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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
    {
        // ...
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 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
    {
        // ...
    }
}

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

Tip

Ви також можете встановити опцію host, коли імпортуєте маршрути , щоб зробити так, щоб вони всі вимагали це ім'я хосту.

Note

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

1
2
3
4
5
6
7
8
9
$crawler = $client->request(
    'GET',
    '/',
    [],
    [],
    ['HTTP_HOST' => 'm.example.com']
    // або отримати значення з якогось параметра контейнера:
    // ['HTTP_HOST' => 'm.' . $client->getContainer()->getParameter('domain')]
);

Tip

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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'
    ], name: 'about_us')]
    public function about(): Response
    {
        // ...
    }
}

Note

При використанні PHP-атрибутів для локалізованих маршрутів, вам потрібно використовувати параметр за ім'ям path, щоб вказати масив шляхів.

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

Tip

Коли застосунок використовує повні локалі "мова + територія" (наприклад, fr_FR, fr_BE), якщо URL однакові у всіх пов'язаних локалях, маршрути можуть використовувати лише частину мови (наприклад, fr), щоб уникнути повторення одних і тих самих URL.

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

1
2
3
4
5
6
7
# config/routes/annotations.yaml
controllers:
    resource: '../../src/Controller/'
    type: annotation
    prefix:
        en: '' # не додавайте до URL префікс для англійської, локалі за замовчуванням
        nl: '/nl'

Інша розповсюджена вимога - розміщувати веб-сайт на різних доменах у відповідності до локалі. Це може бути зроблено шляхом визначення різних хостів для кожної локалі.

1
2
3
4
5
6
7
# config/routes/attributes.yaml
controllers:
    resource: '../../src/Controller/'
    type: attribute
    host:
        en: 'www.example.com'
        nl: 'www.example.nl'

Маршрути без стану

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

Для деталей, див. HTTP-кеш.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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
    {
        // ...
    }
}

Тепер, якщо сесія використовується, застосунок повідомить про неї, засновуючись на вашому параметрі kernel.debug:

Це допоможе вам зрозуміти, і, сподіваємося, виправити неочікувану поведінку вашого додатку.

Генерування URL

Системи маршрутизації двосторонні:

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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']);

        // ...
    }
}

Note

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

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

Caution

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

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

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

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

Впровадьте сервіс Symfony router у ваші власні сервіси та використайте його метод generate(). При використанні автомонтування сервісів, вам знадобиться лише додати аргумент у конструктор сервісу та типізувати його класом UrlGeneratorInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/Service/SomeService.php
namespace App\Service;

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

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

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

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

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

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

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

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

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

Генерування URL у JavaScript

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

1
2
3
<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:

1
2
3
4
5
# config/packages/routing.yaml
framework:
    router:
        # ...
        default_uri: 'https://example.org/my/path/'

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/Command/SomeCommand.php
namespace App\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
// ...

class SomeCommand extends Command
{
    public function __construct(private UrlGeneratorInterface $urlGenerator)
    {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // згенеруйте URL без аргументів маршруту
        $signUpPage = $this->router->generate('sign_up');

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

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

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

        // ...
    }
}

Note

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

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

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

Замість цього, спробуйте згенерувати URL та спіймати RouteNotFoundException, викликане, коли маршрут не існує:

1
2
3
4
5
6
7
8
9
use Symfony\Component\Routing\Exception\RouteNotFoundException;

// ...

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

Форсування HTTPS у згенерованих URL

Note

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

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

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

1
2
3
4
# config/services.yaml
parameters:
    router.request_context.scheme: 'https'
    asset.request_context.secure: true

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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
    {
        // ...
    }
}

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

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

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

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

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

1
2
3
4
5
# config/routes/attributes.yaml
controllers:
    resource: '../../src/Controller/'
    type: attribute
    schemes: [https]

Note

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

Підписання URI

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

Symfony надає утиліту для підписання URI через сервіс UriSigner, який ви можете впровадити у ваші сервіси або контролери:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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'

        // перевірити підпис URL
        $uriSignatureIsValid = $this->uriSigner->check($signedUrl);
        // $uriSignatureIsValid = true

        // якщо у вас є доступ до поточного обʼєкта Request, ви можете використати цей
        // інший метод для передачі всього обʼєкта Request замість URI:
        $uriSignatureIsValid = $this->uriSigner->checkRequest($request);
    }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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 з явним терміном завершення дії
        $signedUrl = $this->uriSigner->sign($url, new \DateTimeImmutable('2050-01-01'));
        // $signedUrl = 'https://example.com/foo/bar?sort=desc&_expiration=2524608000&_hash=e4a21b9'

        // якщо ви передасте \DateInterval, відтепер він буде додаватися для отримання терміну завершення дії
        $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'
    }
}

Note

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

7.1

Функція додавання дати завершення терміну дії для підписаного URI була представлена в Symfony 7.1.

Налагодження проблем

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

1
2
Контролер "App\\Controller\\BlogController::show()" вимагає, щоб ви надали
значення для аргументу "$slug".

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

1
2
3
4
public function show(string $slug): Response
{
    // ...
}

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

1
2
Деякі обов'язкові параметри не мають ("slug") для генерування URL для маршруту
"blog_show".

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

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

або у Twig:

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