Дата обновления перевода 2021-06-09

Маршрутизация

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

Создание маршрутов

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

Создание маршрутов в виде атрибутов или аннотаций

В PHP 8, вы можете использовать нативные атрибуты для немедленной конфигурации маршрутов. В PHP 7, где атрибуты недоступны, вы можете использовать вместо этого аннотации, предоставленные библиотекой Аннотаций Doctrine.

В случае, если вы хотите использовать аннотации вместо атрибутов, единожды выполните эту команду в вашем приложении, чтобы их включить:

1
$ composer require doctrine/annotations

New in version 5.2: Возможность использовать PHP-атрибуты для конфигурации маршрутов, была представлена в Symfony 5.2. Раньше, Аннотации Doctrine были единственным способом аннотировать действия контроллера конфигурацией маршрутизации.

Эта команда также создает следующий файл конфигурации:

1
2
3
4
5
6
7
8
# config/routes/annotations.yaml
controllers:
    resource: ../../src/Controller/
    type: annotation

kernel:
    resource: ../../src/Kernel.php
    type: annotation

Эта конфигурация сообщает Symfony, что нужно искать маршруты, определенные как аннотации в любом PHP-классе, хранящемся в каталоге src/Controller/.

Представьте, что вы хотите определить маршрут для URL /blog в вашем приложении. Чтобы сделать это, создайте класс контроллера как показано ниже:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        /**
         * @Route("/blog", name="blog_list")
         */
        public function list()
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        #[Route('/blog', name: 'blog_list')]
        public function list()
        {
            // ...
        }
    }
    

Эта конфигурация определяет маршрут под названием 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:

  • YAML
    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
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <!-- значение контроллера ммеет формат 'controller_class::method_name' -->
        <route id="blog_list" path="/blog"
               controller="App\Controller\BlogController::list"/>
    
        <!-- еслм действие реализуется как метод __invoke() класса контроллера,
             вы можете пропустить часть '::method_name':
             controller="App\Controller\BlogController"/> -->
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // config/routes.php
    use App\Controller\BlogController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('blog_list', '/blog')
            // значение контроллера ммеет формат [controller_class, method_name]
            ->controller([BlogController::class, 'list'])
    
            // еслм действие реализуется как метод __invoke() класса контроллера,
            // вы можете пропустить часть '::method_name':
            // ->controller(BlogController::class)
        ;
    };
    

New in version 5.1: Начиная с Symfony 5.1, по умолчанию Symfony загружает только маршруты, определенные в формате YAML. Если вы определяете маршруты в формате XML и/или PHP, обновите файл src/Kernel.php, чтобы добавить поддержку расширений файлов .xml и .php.

Совпадение HTTP-методов

По умолчанию, маршруты совпадают с любым глаголом HTTP (GET, POST, PUT, и др.). Используйте опцию methods, чтобы ограничить глаголы, на которые долшжен реагировать каждый маршрут:

  • Annotations
     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
    // src/Controller/BlogApiController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\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
        {
            // ... редактировать пост
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // src/Controller/BlogApiController.php
    namespace App\Controller;
    
    // ...
    
    class BlogApiController extends AbstractController
    {
        #[Route('/api/posts/{id}', methods: ['GET', 'HEAD'])]
        public function show(int $id)
        {
            // ... вернуть JSON-ответ с постом
        }
    
        #[Route('/api/posts/{id}', methods: ['PUT'])]
        public function edit(int $id)
        {
            // ... редактировать пост
        }
    }
    
  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # 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
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="api_post_show" path="/api/posts/{id}"
            controller="App\Controller\BlogApiController::show"
            methods="GET|HEAD"/>
    
        <route id="api_post_edit" path="/api/posts/{id}"
            controller="App\Controller\BlogApiController::edit"
            methods="PUT"/>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // config/routes.php
    use App\Controller\BlogApiController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('api_post_show', '/api/posts/{id}')
            ->controller([BlogApiController::class, 'show'])
            ->methods(['GET', 'HEAD'])
        ;
        $routes->add('api_post_edit', '/api/posts/{id}')
            ->controller([BlogApiController::class, 'edit'])
            ->methods(['PUT'])
        ;
    };
    

Tip

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

Совпадающие выражения

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

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // src/Controller/DefaultController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\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
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // src/Controller/DefaultController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\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()
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    7
    # 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%'"
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="contact" path="/contact" controller="App\Controller\DefaultController::contact">
            <condition>context.getMethod() in ['GET', 'HEAD'] and request.headers.get('User-Agent') matches '/firefox/i'</condition>
            <!-- выражения также могут включать в себя параметры конфигурации: -->
            <!-- <condition>request.headers.get('User-Agent') matches '%app.allowed_browsers%'</condition> -->
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // config/routes.php
    use App\Controller\DefaultController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('contact', '/contact')
            ->controller([DefaultController::class, 'contact'])
            ->condition('context.getMethod() in ["GET", "HEAD"] and request.headers.get("User-Agent") matches "/firefox/i"')
            // выражения также могут включать в себя параметры конфигурации:
            // 'request.headers.get("User-Agent") matches "%app.allowed_browsers%"'
        ;
    };
    

Значение опции condition - это любое валдиное выражение ExpressionLanguage и может использовать любую из этих переменных, созданных Symfony:

context
Экземпляр RequestContext, который содержит наиболее фунламентальную информацию о сопоставляемом маршруте.
request
Объект Запроса Symfony, который представляет текущий запрос.

За кулисами. выражения компилируются в чистый 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

+-------------+---------------------------------------------------------+
| Свойство    | Значение                                                |
+-------------+---------------------------------------------------------+
| Route Name  | app_lucky_number                                        |
| Path        | /lucky/number/{max}                                     |
| ...         | ...                                                     |
| Options     | compiler_class: Symfony\Component\Routing\RouteCompiler |
|             | utf8: true                                              |
+-------------+---------------------------------------------------------+

Другая команда называется router:match и она показывает, какой маршрут будет совпадать с заданным URL. It’s useful to find out why some URL is not executing the controller action that you expect:

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}:

  • Annotations
     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\Annotation\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'
    
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        // ...
    
        #[Route('/blog/{slug}', name: 'blog_show')]
        public function show(string $slug)
        {
            // $slug будет равен динамической части URL
            // например, /blog/yay-routing, затем $slug='yay-routing'
    
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    # config/routes.yaml
    blog_show:
        path:       /blog/{slug}
        controller: App\Controller\BlogController::show
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_show" path="/blog/{slug}"
               controller="App\Controller\BlogController::show"/>
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // config/routes.php
    use App\Controller\BlogController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('blog_show', '/blog/{slug}')
            ->controller([BlogController::class, 'show'])
        ;
    };
    

Имя переменной части ({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:

  • Annotations
     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
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\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(string $slug): Response
        {
            // ...
        }
    }
    
  • Attributes
     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\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
        public function list(int $page)
        {
            // ...
        }
    
        #[Route('/blog/{slug}', name: 'blog_show')]
        public function show($slug)
        {
            // ...
        }
    }
    
  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    # config/routes.yaml
    blog_list:
        path:       /blog/{page}
        controller: App\Controller\BlogController::list
        requirements:
            page: '\d+'
    
    blog_show:
        path:       /blog/{slug}
        controller: App\Controller\BlogController::show
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
            <requirement key="page">\d+</requirement>
        </route>
    
        <route id="blog_show" path="/blog/{slug}"
               controller="App\Controller\BlogController::show"/>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    // config/routes.php
    use App\Controller\BlogController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('blog_list', '/blog/{page}')
            ->controller([BlogController::class, 'list'])
            ->requirements(['page' => '\d+'])
        ;
    
        $routes->add('blog_show', '/blog/{slug}')
            ->controller([BlogController::class, 'show'])
        ;
        // ...
    };
    

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

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

Tip

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

Tip

Параметры также поддерживают свойства PCRE Unicode, которые являются последовательностями экранирования, совпадаюшими с общими типами символов. Например, \p{Lu} совпадает с любым заглавным символом на любом языке, \p{Greek} совпадает с любым греческим символом и т.д.

Note

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

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

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        /**
         * @Route("/blog/{page<\d+>}", name="blog_list")
         */
        public function list(int $page): Response
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        #[Route('/blog/{page<\d+>}', name: 'blog_list')]
        public function list(int $page)
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    # config/routes.yaml
    blog_list:
        path:       /blog/{page<\d+>}
        controller: App\Controller\BlogController::list
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_list" path="/blog/{page<\d+>}"
               controller="App\Controller\BlogController::list"/>
    
        <!-- ... -->
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // config/routes.php
    use App\Controller\BlogController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('blog_list', '/blog/{page<\d+>}')
            ->controller([BlogController::class, 'list'])
        ;
        // ...
    };
    

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

В предыдущем примере, URL blog_list - /blog/{page}. Если пользователи посещают /blog/1, он будет совпадать. Но если они посетят /blog, он не будет совпадать. Как только вы добавите к маршруту параметр, он должен иметь значение.

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

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        /**
         * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"})
         */
        public function list(int $page = 1): Response
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        #[Route('/blog/{page}', name: 'blog_list', requirements: ['page' => '\d+'])]
        public function list(int $page = 1)
        {
            // ...
        }
    }
    
  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    # config/routes.yaml
    blog_list:
        path:       /blog/{page}
        controller: App\Controller\BlogController::list
        defaults:
            page: 1
        requirements:
            page: '\d+'
    
    blog_show:
        # ...
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_list" path="/blog/{page}" controller="App\Controller\BlogController::list">
            <default key="page">1</default>
    
            <requirement key="page">\d+</requirement>
        </route>
    
        <!-- ... -->
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // config/routes.php
    use App\Controller\BlogController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('blog_list', '/blog/{page}')
            ->controller([BlogController::class, 'list'])
            ->defaults(['page' => 1])
            ->requirements(['page' => '\d+'])
        ;
    };
    

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

Caution

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

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

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

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        /**
         * @Route("/blog/{page<\d+>?1}", name="blog_list")
         */
        public function list(int $page): Response
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        #[Route('/blog/{page<\d+>?1}', name: 'blog_list')]
        public function list(int $page)
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    # config/routes.yaml
    blog_list:
        path:       /blog/{page<\d+>?1}
        controller: App\Controller\BlogController::list
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_list" path="/blog/{page<\d+>?1}"
               controller="App\Controller\BlogController::list"/>
    
        <!-- ... -->
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // config/routes.php
    use App\Controller\BlogController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('blog_list', '/blog/{page<\d+>?1}')
            ->controller([BlogController::class, 'list'])
        ;
    };
    

Tip

Чтобы дать значение по умолчанию null любому параметру, ничего не добавляйте после символа ? (например, /blog/{page?}).

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

New in version 5.1: Параметр priority был представлен в Symfony 5.1

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

  • Annotations
     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
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        /**
         * Этот маршрут имеет жадный паттерн и определяется первым.
         *
         * @Route("/blog/{slug}", name="blog_show")
         */
        public function show(string $slug)
        {
            // ...
        }
    
        /**
         * Этот маршрут не может быть сопоставлен без определения приоритета выше, чем 0.
         *
         * @Route("/blog/list", name="blog_list", priority=2)
         */
        public function list()
        {
            // ...
        }
    }
    
  • Attributes
     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\Annotation\Route;
    
    class BlogController extends AbstractController
    {
        /**
         * Этот маршрут имеет жадный паттерн и определяется первым.
         */
        #[Route('/blog/{slug}', name: 'blog_show')]
        public function show(string $slug)
        {
            // ...
        }
    
        /**
         * Этот маршрут не может быть сопоставлен без определения приоритета выше, чем 0.
         */
        #[Route('/blog/list', name: 'blog_list', priority: 2)]
        public function list()
        {
            // ...
        }
    }
    

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

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

Распространенной потребностью маршрутизации является конверсия значения, хранящегося в некотором параметре (например, целое число, действующее, как ID пользователя), в другое значение. Эта функция называется “param converter” и доступна только при использовании аннотаций для определения маршрутов.

Чтобы добавить поддержку “param converters”, нам нужен SensioFrameworkExtraBundle:

1
$ composer require sensio/framework-extra-bundle

Теперь, оставьте предыдущую конфигурацию маршрута, но измените аргументы действия контроллера. Вместо 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\Annotation\Route;

class BlogController extends AbstractController
{
    // ...

    /**
     * @Route("/blog/{slug}", name="blog_show")
     */
    public function show(BlogPost $post): Response
    {
        // $post это объект, чей слаг сооветствует параметру маршрутизации

        // ...
    }
}

Если ваши аргументы контроллера включают в себя подсказки для объектов (BlogPost в этом случае), “param converter” делает запрос в базу данных, чтобы найти объект, использующий параметры запроса (slug в этом случае). Если объект не найден, Symfony автоматически генерирует ответ 404.

Прочтите `полную документацию param converter`_, чтобы узнать о преобразователях, предоставленных Symfony, и о том, как их сконфигурировать.

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

В дополнение к вашим собственным параметрам, маршруты могут иметь любые следующие параметры, созданные Symfony:

_controller
Этот параметр используется для определения того, какой контроллер и действие выполняется при совпадении маршрута.
_format
Совпавшее значение используется для установки “request format” объекта Request. Это используется для таких вещей, как установка Content-Type ответа (например, формат json переводится в Content-Type для application/json).
_fragment
Используется для установки идентификатора фрагмента, что является последней необязательной частью URL, которая начинается с символа # и используется для идентификации части документа.
_locale
Используется для установки локали в запросе.

Вы можете добавить эти атрибуты (кроме _fragment) как в индивидуальных маршрутах, так и в импортированных. Symfony определяет некоторые особые атрибуты с одинаковым именем (кроме нижнего подчеркивания в начале), поэтому вам может быть легче их определить:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // src/Controller/ArticleController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class ArticleController extends AbstractController
    {
        /**
         * @Route(
         *     "/articles/{_locale}/search.{_format}",
         *     locale="en",
         *     format="html",
         *     requirements={
         *         "_locale": "en|fr",
         *         "_format": "html|xml",
         *     }
         * )
         */
        public function search(): Response
        {
        }
    }
    
  • Attributes
     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()
        {
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # config/routes.yaml
    article_search:
      path:        /articles/{_locale}/search.{_format}
      controller:  App\Controller\ArticleController::search
      locale:      en
      format:      html
      requirements:
          _locale: en|fr
          _format: html|xml
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="article_search"
            path="/articles/{_locale}/search.{_format}"
            controller="App\Controller\ArticleController::search"
            locale="en"
            format="html">
    
            <requirement key="_locale">en|fr</requirement>
            <requirement key="_format">html|rss</requirement>
    
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    // config/routes.php
    namespace Symfony\Component\Routing\Loader\Configurator;
    
    use App\Controller\ArticleController;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('article_show', '/articles/{_locale}/search.{_format}')
            ->controller([ArticleController::class, 'search'])
            ->locale('en')
            ->format('html')
            ->requirements([
                '_locale' => 'en|fr',
                '_format' => 'html|rss',
            ])
        ;
    };
    

Дополнительные параметры

В опции маршрута defaults вы можете по желанию определить параметры, не включенные в конфигурацию маршрута. Это полезно для передачи дополнительных аргументов контроллерам маршрутов:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\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
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Component\Routing\Annotation\Route;
    
    class BlogController
    {
        #[Route('/blog/{page}', name: 'blog_index', defaults: ['page' => 1, 'title' => 'Hello world!'])]
        public function index(int $page, string $title)
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/routes.yaml
    blog_index:
        path:       /blog/{page}
        controller: App\Controller\BlogController::index
        defaults:
            page: 1
            title: "Hello world!"
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="blog_index" path="/blog/{page}" controller="App\Controller\BlogController::index">
            <default key="page">1</default>
            <default key="title">Hello world!</default>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // config/routes.php
    use App\Controller\BlogController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('blog_index', '/blog/{page}')
            ->controller([BlogController::class, 'index'])
            ->defaults([
                'page'  => 1,
                'title' => 'Hello world!',
            ])
        ;
    };
    

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

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

Возможным решением будет изменение требований параметра, чтобы они были более вольными:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/DefaultController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class DefaultController extends AbstractController
    {
        /**
         * @Route("/share/{token}", name="share", requirements={"token"=".+"})
         */
        public function share($token): Response
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // src/Controller/DefaultController.php
    namespace App\Controller;
    
    use Symfony\Component\Routing\Annotation\Route;
    
    class DefaultController
    {
        #[Route('/share/{token}', name: 'share', requirements: ['token' => '.+'])]
        public function share($token)
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    # config/routes.yaml
    share:
        path:       /share/{token}
        controller: App\Controller\DefaultController::share
        requirements:
            token: .+
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="share" path="/share/{token}" controller="App\Controller\DefaultController::share">
            <requirement key="token">.+</requirement>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // config/routes.php
    use App\Controller\DefaultController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('share', '/share/{token}')
            ->controller([DefaultController::class, 'share'])
            ->requirements([
                'token' => '.+',
            ])
        ;
    };
    

Note

Если маршрут определяет несколько параметров, и вы применяете это вольное регулярное выражение ко всем, вы можете получить неожиданные результаты. Например, если определение маршрута - /share/{path}/{token} и как path, так и token принимают /. token получает только последний путь и остаток совпадения соотносится с первым аргументом (path).

Note

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

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

Часто группа маршрутов будет иметь некоторые общие опции (например, все маршруты, связанные с блогом, начинаются с /blog). Поэтому Symfony имеет функцию общей конфигурации маршрутов.

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

  • Annotations
     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
    // src/Controller/BlogController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\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(Post $post): Response
        {
            // ...
        }
    }
    
  • Attributes
     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\Component\Routing\Annotation\Route;
    
    #[Route('/blog', requirements: ['_locale' => 'en|es|fr'], name: 'blog_')]
    class BlogController
    {
        #[Route('/{_locale}', name: 'index')]
        public function index()
        {
            // ...
        }
    
        #[Route('/{_locale}/posts/{slug}', name: 'show')]
        public function show(Post $post)
        {
            // ...
        }
    }
    
  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # config/routes/annotations.yaml
    controllers:
        resource: '../../src/Controller/'
        type: annotation
        # это добавляется к началу всех импортированных URL маршрутов
        prefix: '/blog'
        # это добавляется к началу всех импортированных имен маршрутов
        name_prefix: 'blog_'
        # эти требования добавляются ко всем импортированным маршрутам
        requirements:
            _locale: 'en|es|fr'
    
        # Импортированный маршрут с пустым URL станет "/blog/"
        # Раскомментируйте эту опцию, чтобы сделать этот URL "/blog"
        # trailing_slash_on_root: false
    
        # вы по желанию можете исключить некоторые файлы/подкаталоги при загрузке аннотаций
        # exclude: '../../src/Controller/{DebugEmailController}.php'
    
  • XML
     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
    <!-- config/routes/annotations.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <!--
            значение 'prefix' добавляется к началу всех импортированных URL маршрутов
            значение 'name-prefix' добавляется к началу всех импортированных имен маршрутов
            опция 'exclude' определяет файлы или подкаталоги, которые нужно игнорировать при загрузке аннотаций
        -->
        <import resource="../../src/Controller/"
            type="annotation"
            prefix="/blog"
            name-prefix="blog_"
            exclude="../../src/Controller/{DebugEmailController}.php">
            <!-- these requirements are added to all imported routes -->
            <requirement key="_locale">en|es|fr</requirement>
        </import>
    
        <!-- Импортированный маршрут с пустым URL станет "/blog/"
             Раскомментируйте эту опцию, чтобы сделать этот URL "/blog" -->
        <import resource="../../src/Controller/" type="annotation"
                prefix="/blog"
                trailing-slash-on-root="false">
                <!-- ... -->
        </import>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // config/routes/annotations.php
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        // используйте необязательный четвертый аргумент import(), чтобы исключить некоторые файлы
        // или подкаталоги при загрузке аннотаций
        $routes->import('../../src/Controller/', 'annotation')
            // это добавляется к началу всех импортированных URL маршрутов
            ->prefix('/blog')
    
            // Импортированный маршрут с пустым URL станет "/blog/"
            // Передайте FALSE в качестве второго аргументы, чтобы сделать этот URL "/blog"
            // ->prefix('/blog', false)
    
            // это добавляется к началу всех импортированных имен маршрутов
            ->namePrefix('blog_')
    
            // эти требования добавляются ко всем импортированным маршрутам
            ->requirements(['_locale' => 'en|es|fr'])
    
            // вы по желанию можете исключить некоторые файлы/подкаталоги при загрузке аннотаций
            ->exclude('../../src/Controller/{DebugEmailController}.php')
        ;
    };
    

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

See also

Symfony может импортировать маршруты из других источников и вы можете даже создавать собственный загрузчик маршрутов.

Получение имени и параметров маршрута

Объект 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\Annotation\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, чтобы получить запрос и его атрибуты:

1
2
3
4
5
{% set route_name = app.request.attributes.get('_route') %}
{% set route_parameters = app.request.attributes.get('_route_params') %}

{# use this to get all the available attributes (not only routing ones) #}
{% set all_attributes = app.request.attributes.all %}

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

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

Отображение шаблона прямо из маршрута

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

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

Используйте RedirectController, чтобы перенаправить на другие маршруты и URL:

  • YAML
     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
    
  • XML
     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
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="doc_shortcut" path="/doc"
               controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController">
            <default key="route">doc_page</default>
            <!-- по желанию вы можете определить какие-то аргументы, переданные маршруту -->
            <default key="page">index</default>
            <default key="version">current</default>
            <!-- перенаправляния временные по умолчанию (код 302), но вы можете сделать их постоянными (код 301)-->
            <default key="permanent">true</default>
            <!-- добавьтье это, чтобы сохранить изначальные параметры строки запроса при перенаправлении -->
            <default key="keepQueryParams">true</default>
            <!-- добавьте это, чтобы оставить метод HTTP при перенаправлении. Статус перенаправления изменяется:
                 * для временных перенаправлений, используется статус-код 307 вместо 302
                 * для постоянных перенаправлений, используется статус-код 307 вместо 301 -->
            <default key="keepRequestMethod">true</default>
        </route>
    
        <route id="legacy_doc" path="/legacy/doc"
               controller="Symfony\Bundle\FrameworkBundle\Controller\RedirectController">
            <!-- это значение может быть абсолютным путем или URL -->
            <default key="path">https://legacy.example.com/doc</default>
            <!-- перенаправления временнеые по умолчанию (код 302), но вы можете сделать их постоянными (код 301)-->
            <default key="permanent">true</default>
        </route>
    </routes>
    
  • PHP
     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
    // config/routes.php
    use App\Controller\DefaultController;
    use Symfony\Bundle\FrameworkBundle\Controller\RedirectController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('doc_shortcut', '/doc')
            ->controller(RedirectController::class)
             ->defaults([
                'route' => 'doc_page',
                // по желанию вы можете определить какие-то аргументы, переданные маршруту
                'page' => 'index',
                'version' => 'current',
                // перенаправляния временные по умолчанию (код 302), но вы можете сделать их постоянными (код 301)
                'permanent' => true,
                // добавьтье это, чтобы сохранить изначальные параметры строки запроса при перенаправлении
                'keepQueryParams' => true,
                // добавьте это, чтобы оставить метод HTTP при перенаправлении. Статус перенаправления изменяется:
                // * для временных перенаправлений, используется статус-код 307 вместо 302
                // * для постоянных перенаправлений, используется статус-код 307 вместо 301
                'keepRequestMethod' => true,
            ])
        ;
    
        $routes->add('legacy_doc', '/legacy/doc')
            ->controller(RedirectController::class)
             ->defaults([
                // это значение может быть абсолютным путем или URL
                'path' => 'https://legacy.example.com/doc',
                // перенаправления временнеые по умолчанию (код 302), но вы можете сделать их постоянными (код 301)
                '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 Cовпадает (статус-ответ 200) Делает перенаправление 301 на /foo
/foo/ Делает перенаправление 301 на /foo Cовпадает (статус-ответ 200)

Маршрутизация подкаталогов

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

  • Annotations
     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
    // src/Controller/MainController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class MainController extends AbstractController
    {
        /**
         * @Route("/", name="mobile_homepage", host="m.example.com")
         */
        public function mobileHomepage(): Response
        {
            // ...
        }
    
        /**
         * @Route("/", name="homepage")
         */
        public function homepage(): Response
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // src/Controller/MainController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class MainController extends AbstractController
    {
        #[Route('/', name: 'mobile_homepage', host: 'm.example.com')]
        public function mobileHomepage()
        {
            // ...
        }
    
        #[Route('/', name: 'homepage')]
        public function homepage()
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # config/routes.yaml
    mobile_homepage:
        path:       /
        host:       m.example.com
        controller: App\Controller\MainController::mobileHomepage
    
    homepage:
        path:       /
        controller: App\Controller\MainController::homepage
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="mobile_homepage"
            path="/"
            host="m.example.com"
            controller="App\Controller\MainController::mobileHomepage"/>
    
        <route id="homepage" path="/" controller="App\Controller\MainController::homepage"/>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    // config/routes.php
    use App\Controller\MainController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('mobile_homepage', '/')
            ->controller([MainController::class, 'mobileHomepage'])
            ->host('m.example.com')
        ;
        $routes->add('homepage', '/')
            ->controller([MainController::class, 'homepage'])
        ;
    };
    

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

  • Annotations
     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
    // src/Controller/MainController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\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
        {
            // ...
        }
    }
    
  • Attributes
     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/MainController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class MainController extends AbstractController
    {
        #[Route(
            '/',
            name: 'mobile_homepage',
            host: '{subdomain}.example.com',
            defaults: ['subdomain' => 'm'],
            requirements: ['subdomain' => 'm|mobile'],
        )]
        public function mobileHomepage()
        {
            // ...
        }
    
        #[Route('/', name: 'homepage')]
        public function homepage()
        {
            // ...
        }
    }
    
  • YAML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    # 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
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="mobile_homepage"
            path="/"
            host="{subdomain}.example.com"
            controller="App\Controller\MainController::mobileHomepage">
            <default key="subdomain">m</default>
            <requirement key="subdomain">m|mobile</requirement>
        </route>
    
        <route id="homepage" path="/" controller="App\Controller\MainController::homepage"/>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // config/routes.php
    use App\Controller\MainController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('mobile_homepage', '/')
            ->controller([MainController::class, 'mobileHomepage'])
            ->host('{subdomain}.example.com')
            ->defaults([
                'subdomain' => 'm',
            ])
            ->requirements([
                'subdomain' => 'm|mobile',
            ])
        ;
        $routes->add('homepage', '/')
            ->controller([MainController::class, 'homepage'])
        ;
    };
    

В примере выше, параметр subdomain определяет значение по умолчанию, потому что иначе вам нужно включать значение домена каждый раз, когда вы генерируете URL с использованием этих маршрутов.

Tip

Вы также можете установить опцию host, когда импортируете маршруты, чтобы сделать так, чтобы они все требовали это имя хоста.

Note

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

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

Tip

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

New in version 5.2: Поддержка значений встроенных параметров по умолчанию в хостах была представлена в Symfony 5.2. До Symfony 5.2, они поддерживались только в пути.

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

Если ваше приложение переводится на несколько языков, каждый маршрут может определять другой URL по каждой локали перевода. Это помогает избежать необходимости дублированния маршрутов, что также уменьшает потенциальные баги:

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // src/Controller/CompanyController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class CompanyController extends AbstractController
    {
        /**
         * @Route({
         *     "en": "/about-us",
         *     "nl": "/over-ons"
         * }, name="about_us")
         */
        public function about(): Response
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/CompanyController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class CompanyController extends AbstractController
    {
        #[Route(path: [
            'en' => '/about-us',
            'nl' => '/over-ons'
        ], name: 'about_us')]
        public function about()
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    6
    # config/routes.yaml
    about_us:
        path:
            en: /about-us
            nl: /over-ons
        controller: App\Controller\CompanyController::about
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="about_us" controller="App\Controller\CompanyController::about">
            <path locale="en">/about-us</path>
            <path locale="nl">/over-ons</path>
        </route>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // config/routes.php
    use App\Controller\CompanyController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('about_us', [
            'en' => '/about-us',
            'nl' => '/over-ons',
        ])
            ->controller([CompanyController::class, 'about'])
        ;
    };
    

Note

При использовании PHP-атрибутов для локализованных маршрутов, вам нужно использовать параметр по имени path, чтобы указать массив путей.

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

Tip

Когда приложение использует полные локали “язык + территория” (например, fr_FR, fr_BE), если URL одинаковы во всех связанных локалях, маршруты могут спользовать только часть языка (например, fr), чтобы избежать повторения одних и тех же URL.

Распространенное требования для меджународных приложений - добавлять ко всем маршрутам префикс локали. Это может быть сделано путем определения разных префиксов для каждой локали (и установки пустого префикса для локали по умолчанию, если вы этого хотите):

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/routes/annotations.yaml
    controllers:
        resource: '../../src/Controller/'
        type: annotation
        prefix:
            en: '' # don't prefix URLs for English, the default locale
            nl: '/nl'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- config/routes/annotations.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import resource="../../src/Controller/" type="annotation">
            <!-- don't prefix URLs for English, the default locale -->
            <prefix locale="en"></prefix>
            <prefix locale="nl">/nl</prefix>
        </import>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    // config/routes/annotations.php
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->import('../../src/Controller/', 'annotation')
            ->prefix([
                // don't prefix URLs for English, the default locale
                'en' => '',
                'nl' => '/nl'
            ])
        ;
    };
    

Другое распространенное требование - размещать веб-сайт на разных доменах в соответствии с локалью. Это может быть сделано путем определения разных хостов для каждой локали.

New in version 5.1: Возможность определять массив хостов была представлена в Symfony 5.1.

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/routes/annotations.yaml
    controllers:
        resource: '../../src/Controller/'
        type: annotation
        host:
            en: 'https://www.example.com'
            nl: 'https://www.example.nl'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- config/routes/annotations.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
        <import resource="../../src/Controller/" type="annotation">
            <host locale="en">https://www.example.com</host>
            <host locale="nl">https://www.example.nl</host>
        </import>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // config/routes/annotations.php
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    return function (RoutingConfigurator $routes) {
        $routes->import('../../src/Controller/', 'annotation')
            ->host([
                'en' => 'https://www.example.com',
                'nl' => 'https://www.example.nl'
            ])
        ;
    };
    

Бесструктурные маршруты

New in version 5.1: Опция stateless была представлена в Symfony 5.1.

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

Для деталей, см. HTTP Cache.

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

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    // src/Controller/MainController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class MainController extends AbstractController
    {
        /**
         * @Route("/", name="homepage", stateless=true)
         */
        public function homepage()
        {
            // ...
        }
    }
    
  • Attributes
     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\Annotation\Route;
    
    class MainController extends AbstractController
    {
        #[Route('/', name: 'homepage', stateless: true)]
        public function homepage()
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    # config/routes.yaml
    homepage:
        controller: App\Controller\MainController::homepage
        path: /
        stateless: true
    
  • XML
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
        <route id="homepage" controller="App\Controller\MainController::homepage" path="/" stateless="true"/>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // config/routes.php
    use App\Controller\MainController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('homepage', '/')
            ->controller([MainController::class, 'homepage'])
            ->stateless()
        ;
    };
    

Теперь, если сессия используется, приложение сообщит о ней, основываясь на вашем параметре kernel.debug: * enabled: вызовет исключение UnexpectedSessionUsageException * disabled: запишет лог предупреждения

Это поможет вам понять, и, надеемся, исправить неожиданное поведение вашего приложения.

Генерирование URL

Системы маршрутизации двусторонни: 1) они ассоциируют URL с контроллерами (как объясняется в предыдущих разделах); 2) они генерируют URL для заданного маршрута. Генерирование URL из маршрутов позволяет вам не писать значения <a href="..."> вручную в ваших HTML-шаблонах. Также, если URL какого-то маршрута изменяется, вы только должны обновить конфигурацию маршрута и все ссылки будут обновлены.

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

По этой причина каждый маршрут имеет внутреннее имя, которое должно быть уникально в приложении. Если вы не установите имя маршрута ясно с опцией name, Symfony генерирует автоматическое имя, основываясь на контроллере и действии.

Генерирование 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\Annotation\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 как строка запроса:

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

Caution

В то время как объекты преобразуются в строку при использовании в качестве заполнителей, они не преобразуются при использовании в качестве дополнительных параметров. Поэтому, если вы передаете объект (например, Uuid) в качестве значения дополнительного параметра, вам нужно ясно преобразовать его в строку:

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

Если ваш контроллер не расширяется из AbstractController, вам понадобится извлечь сервисы в вашем контроллере и следовать инструции в следующем разделе.

Генерирование URL в сервисах

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

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

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class SomeService
{
    private $router;

    public function __construct(UrlGeneratorInterface $router)
    {
        $this->router = $router;
    }

    public function someMethod()
    {
        // ...

        // сгенерируйте 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, это решение не работает. D таких случаях рассмотрите использование FOSJsRoutingBundle.

Генерирование URL в командах

Генерирование URL в командах работает так же, как генерирование URL в сервисах. Единственная разница в том, что команды не выполняются в HTTP-контексте. Следовательно, если вы генерируете абсолютные URL, вы получите http://localhost/ в качестве имени хоста вместо вашего реального имени хоста.

Решением будет сконфигурировать опцию default_uri, чтобы определить “контекст запроса”, используемы командами, когда они генерируют URL:

  • YAML
    1
    2
    3
    4
    5
    # config/packages/routing.yaml
    framework:
        router:
            # ...
            default_uri: 'https://example.org/my/path/'
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- config/packages/routing.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:framework="http://symfony.com/schema/dic/symfony"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd
            http://symfony.com/schema/dic/symfony
            https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
    
        <framework:config>
            <framework:router default-uri="https://example.org/my/path/">
                <!-- ... -->
            </framework:router>
        </framework:config>
    </container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    // config/packages/routing.php
    use Symfony\Config\FrameworkConfig;
    
    return static function (FrameworkConfig $framework) {
        $framework->router()->defaultUri('https://example.org/my/path/');
    };
    

New in version 5.1: Опция default_uri была представлена в Symfony 5.1.

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

// 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;
use Symfony\Component\Routing\RouterInterface;
// ...

class SomeCommand extends Command
{
    private $router;

    public function __construct(RouterInterface $router)
    {
        parent::__construct();

        $this->router = $router;
    }

    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, вызванное, когда маршрут не существует:

use Symfony\Component\Routing\Exception\RouteNotFoundException;

// ...

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

Форсирование HTTPS в сгенерированных URL

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

  • YAML
    1
    2
    3
    4
    # config/services.yaml
    parameters:
        router.request_context.scheme: 'https'
        asset.request_context.secure: true
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <!-- config/services.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <container xmlns="http://symfony.com/schema/dic/services"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <parameters>
            <parameter key="router.request_context.scheme">https</parameter>
            <parameter key="asset.request_context.secure">true</parameter>
        </parameters>
    
    </container>
    
  • PHP
    1
    2
    3
    // config/services.php
    $container->setParameter('router.request_context.scheme', 'https');
    $container->setParameter('asset.request_context.secure', true);
    

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

  • Annotations
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    // src/Controller/SecurityController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\Routing\Annotation\Route;
    
    class SecurityController extends AbstractController
    {
        /**
         * @Route("/login", name="login", schemes={"https"})
         */
        public function login(): Response
        {
            // ...
        }
    }
    
  • Attributes
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    // src/Controller/SecurityController.php
    namespace App\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
    use Symfony\Component\Routing\Annotation\Route;
    
    class SecurityController extends AbstractController
    {
        #[Route('/login', name: 'login', schemes: ['https'])]
        public function login()
        {
            // ...
        }
    }
    
  • YAML
    1
    2
    3
    4
    5
    # config/routes.yaml
    login:
        path:       /login
        controller: App\Controller\SecurityController::login
        schemes:    [https]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <!-- config/routes.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <route id="login" path="/login" schemes="https"
               controller="App\Controller\SecurityController::login"/>
    </routes>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    // config/routes.php
    use App\Controller\SecurityController;
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->add('login', '/login')
            ->controller([SecurityController::class, 'login'])
            ->schemes(['https'])
        ;
    };
    

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 на все маршруты, определенные как аннотации:

  • YAML
    1
    2
    3
    4
    5
    6
    # config/routes/annotations.yaml
    controllers:
        resource: '../../src/Controller/'
        type: annotation
        defaults:
            schemes: [https]
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- config/routes/annotations.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <routes xmlns="http://symfony.com/schema/routing"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://symfony.com/schema/routing
            https://symfony.com/schema/routing/routing-1.0.xsd">
    
        <import resource="../../src/Controller/" type="annotation">
            <default key="schemes">HTTPS</default>
        </import>
    </routes>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // config/routes/annotations.php
    use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
    
    return function (RoutingConfigurator $routes) {
        $routes->import('../../src/Controller/', 'annotation')
            ->schemes(['https'])
        ;
    };
    

Note

Компонент Безопасность предоставляет другой способ форсирования HTTP или HTTPS через настройку requires_channel.

Отладка проблем

Вот некоторые распространенные ошибки, которые вы можете увидеть при работе с маршрутизацией:

Controller “App\Controller\BlogController::show()” требует, чтобы вы предоставили значение для аргумента “$slug”.

Это происходит, когда ваш метод контроллера имеет аргумент (например, $slug):

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

Но ваш путь маршруте не имеет параметра {slug} (например, он /blog/show). Добавьте {slug} к вашему пути маршрута: /blog/show/{slug} или дайте аргументу значение по умолчанию (т.e. $slug = null).

Некоторые обязательные параметры не имеют (“slug”) для генерирования URL для маршрута “blog_show”.

Это означает, что вы пробуете сгенерировать URL к маршруту blog_show, но не передаете значение slug (что обязательно, так как оно имеет параметр {slug} в пути маршрута). Чтобы исправить это, передайте значение slug при генерировании маршрута:

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

или, в Twig:

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

Узнайте больше о маршрутизации

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.