Маршрутизация¶
Каждое серьезное веб-приложение должно иметь «красивые» URL. Это значит забыть
о таких некрасивых URL, как index.php?article_id=57
, и заменить
их на что-то типа /read/intro-to-symfony
.
Однако гибкость имеет ещё большее значение. Что если вам нужно поменять URL
страницы с /blog
на /news
? Сколько ссылок вам нужно отыскать и обновить,
чтобы внести изменения? Если вы используете маршрутизатор Symfony, то изменения
сделать легко.
Создание маршрутов¶
Для начала, установите пакет аннотаций:
1 | $ composer require annotations
|
Маршрут - это карта от пути URL к контроллеру. Представьте, что вы хотите один
маршрут, точно совпадающий с /blog
, а второй - более динамический, который
может совпадать с любым URL, вроде /blog/my-post
или /blog/all-about-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 25 26 27 28 29 30 31
// src/Controller/BlogController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Routing\Annotation\Route; class BlogController extends Controller { /** * Matches /blog exactly * * @Route("/blog", name="blog_list") */ public function list() { // ... } /** * Matches /blog/* * * @Route("/blog/{slug}", name="blog_show") */ public function show($slug) { // $slug будет равняться динамической части URL // e.g. at /blog/yay-routing, then $slug='yay-routing' // ... } }
- YAML
1 2 3 4 5 6 7 8
# config/routes.yaml blog_list: path: /blog controller: App\Controller\BlogController::list 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 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 http://symfony.com/schema/routing/routing-1.0.xsd"> <route id="blog_list" controller="App\Controller\BlogController::list" path="/blog" > <!-- settings --> </route> <route id="blog_show" controller="App\Controller\BlogController::show" path="/blog/{slug}"> <!-- settings --> </route> </routes>
- PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// config/routes.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use App\Controller\BlogController; $collection = new RouteCollection(); $collection->add('blog_list', new Route('/blog', array( '_controller' => [BlogController::class, 'list'] ))); $collection->add('blog_show', new Route('/blog/{slug}', array( '_controller' => [BlogController::class, 'show'] ))); return $collection;
Благодаря этим двум маршрутам:
- Если пользователь переходит в
/blog
, первый маршрут подходит и выполняетсяlist()
; - Если вользователь переходит в
/blog/*
, второй маршрут подходит и выполняетсяshow()
. Так как путь маршрута/blog/{slug}
, вshow()
определяется переменная$slug
, совпадающая с этим значением. Например, если пользователь переходит на/blog/yay-routing
, тогда$slug
будет равнятьсяyay-routing
.
Если в вашем маршруте есть {placeholder}
, тогда эта часть становится метасимволом
(wildcard): она подходит под любое значение. Ваш контроллер теперь может также иметь
аргумент под названием $placeholder
(названия метасимвола и аргумента
должны совпадать).
Каждый маршрут также имеет внутренне имя: blog_list
и blog_show
. Они
могут быть любыми (при условии, что каждое уникально) и пока не имеют никакого
значения. Вы будете использовать их позже, чтобы генерировать URL.
Добавляем ограничения {метасимволы}¶
Представьте, что путь blog_list
будет содержать список постов блога
с разбивкой по страницам, с URL типа /blog/2
и /blog/3
для страниц 2 и 3.
Если вы измените путь этого маршрута на /blog/{page}
, у вас возникнет
проблема:
- blog_list:
/blog/{page}
будет подходить под/blog/*
; - blog_show:
/blog/{slug}
будет также подходить под/blog/*
.
Когда два маршрута подходят под один и тот же URL, первый маршрут, который
загружается - выигрывает. К сожалению, это значит, что /blog/yay-routing
будет подходить с ``blog_list`. Нехорошо!
Для того, чтобы это исправить, добавьте ограничение, указывающее, что
метасимвол {page}
может подходить только под URL с набором цифр:
- 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/BlogController.php namespace App\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\Routing\Annotation\Route; class BlogController extends Controller { /** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ public function list($page) { // ... } /** * @Route("/blog/{slug}", name="blog_show") */ public function show($slug) { // ... } }
- YAML
1 2 3 4 5 6 7 8 9
# config/routes.yaml blog_list: path: /blog/{page} controller: App\Controller\BlogController::list requirements: page: '\d+' blog_show: # ...
- 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 http://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> <!-- ... --> </routes>
- PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// config/routes.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use App\Controller\BlogController; $collection = new RouteCollection(); $collection->add('blog_list', new Route('/blog/{page}', array( '_controller' => [BlogController::class, 'list'], ), array( 'page' => '\d+' ))); // ... return $collection;
\d+
- это регулярное выражение, которое подходит под набор цифр любой длины. Теперь:
URL | Route | Parameters |
---|---|---|
/blog/2 |
blog_list |
$page = 2 |
/blog/yay-routing |
blog_show |
$slug = yay-routing |
Чтобы узнать о других ограничениях маршрутов, например, HTTP-методе, имени хоста и
динамических выражениях, см. Ограничения маршрутов
.
Устанавливаем значение по умолчанию для {placeholder}¶
В предыдущем примере, blog_list
имеет путь /blog/{page}
. Если
пользователь зайдет на /blog/1
, он подойдёт. Но если пользователь
зайдет на /blog
, то он не подойдёт. Как только вы добавите в
маршрут {placeholder}
, он должен иметь значение.
Так как же сделать так, чтобы blog_list
подходил, когда пользователь
заходит на /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\Controller; use Symfony\Component\Routing\Annotation\Route; class BlogController extends Controller { /** * @Route("/blog/{page}", name="blog_list", requirements={"page"="\d+"}) */ public function list($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 http://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 12 13 14 15 16 17 18 19 20
// config/routes.php use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use App\Controller\BlogController; $collection = new RouteCollection(); $collection->add('blog_list', new Route( '/blog/{page}', array( '_controller' => [BlogController::class, 'list'], 'page' => 1, ), array( 'page' => '\d+' ) )); // ... return $collection;
Теперь, когда пользователь заходит на /blog
, маршрут blog_list
будет подходить, а $page
теперь по умолчанию будет иметь значение 1
.
Список всех ваших маршрутов¶
По мере роста вашего приложения, у вас в итоге появится много маршрутов! Чтобы увидеть их все, выполните:
1 | $ php bin/console debug:router
|
1 2 3 4 5 6 | ------------------------------ -------- -------------------------------------
Имя Метод Путь
------------------------------ -------- -------------------------------------
app_lucky_number ANY /lucky/number/{max}
...
------------------------------ -------- -------------------------------------
|
Пример продвинутой маршрутизации¶
Держа все это в уме, посмотрите на этот продвинутый пример:
- Annotations
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/ArticleController.php // ... class ArticleController extends Controller { /** * @Route( * "/articles/{_locale}/{year}/{slug}.{_format}", * defaults={"_format": "html"}, * requirements={ * "_locale": "en|fr", * "_format": "html|rss", * "year": "\d+" * } * ) */ public function show($_locale, $year, $slug) { } }
- YAML
1 2 3 4 5 6 7 8 9 10
# config/routes.yaml article_show: path: /articles/{_locale}/{year}/{slug}.{_format} controller: App\Controller\ArticleController::show defaults: _format: html requirements: _locale: en|fr _format: html|rss year: \d+
- 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 http://symfony.com/schema/routing/routing-1.0.xsd"> <route id="article_show" path="/articles/{_locale}/{year}/{slug}.{_format}" controller="App\Controller\ArticleController::show"> <default key="_format">html</default> <requirement key="_locale">en|fr</requirement> <requirement key="_format">html|rss</requirement> <requirement key="year">\d+</requirement> </route> </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 Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Route; use App\Controller\ArticleController; $collection = new RouteCollection(); $collection->add( 'article_show', new Route('/articles/{_locale}/{year}/{slug}.{_format}', array( '_controller' => [ArticleController::class, 'show'], '_format' => 'html', ), array( '_locale' => 'en|fr', '_format' => 'html|rss', 'year' => '\d+', )) ); return $collection;
Как вы увидели, этот маршрут будет подходить только если часть {_locale}
URL будет либо en
, либо fr
, и если {year}
будет числом. Этот
маршрут также показывает,как вы можете использовать точку вместо слеша
между заполнителями. URL, подходящие под этот маршрут, могут выглядеть так:
/articles/en/2010/my-post
/articles/fr/2010/my-post.rss
/articles/en/2013/my-latest-post.html
Note
Иногда мы можете захотеть сделать некоторые части ваших маршрутов настраиваемыми
глобально. Symfony предоставляет вам возможность сделать это путем ипользования
параметров контейнера служб. Прочитайте об этом подробнее в разделе
"Параметры контейнеров служб
".
Caution
Имя заполнителя маршрута (placeholder) не может начинаться с цифры и не может иметь больше 32 символов.
Специальные параметры маршрута¶
Как вы уже видели, каждый параметр маршрута или значение по умолчанию в конечном итоге доступен в виде аргумента в методе контроллера. В дополнение к этому есть также четыре специальных параметра, каждый из которых добавляет уникальные возможности внутри вашего приложения:
_controller
- Как вы уже знаете, этот параметр используется для того, чтобы определить какой контроллер будет выполнен, когда маршрут подходит под URL.
_format
- Используется для определения запрашиваемого формата (узнать больше).
_fragment
- Используется для установки идентификатора фрагманта, необязательной последней
части URL, которая начинается с символа
#
и используется для идентификации части документа. _locale
- Используется для установки локали запроса (узнать больше).
Шаблон именования контроллера¶
Значене controller
в ваших маршрутах имеет очень простой формат
CONTROLLER_CLASS::METHOD
. Если ваш контроллер зарегистрирован, как сервис,
то вы также можете использовать только одно двоеточие-разделитель
(например, service_name:index
).
Tip
Чтобы сослаться на действие, реализуемое как метод __invoke()
класса контроллера,
вам не нужно передавать имя метода, а вы можете просто использовать полное имя класса
(например, App\Controller\BlogController
).
Генерирование URL¶
Система маршрутизации также может генерировать URL. В действительности, маршрутизация - это двусторонняя система: прокдадыване пути от URL к контроллеру, а также маршрута обратно к URL.
Для генерации URL, вам нужно будет указать имя маршрута (например, blog_show
)
и любые метасимволы (например, slug = my-blog-post
), используемые в пути для этого
маршрута. С этой информацией, можно с легкостью сгенерировать любой URL:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class MainController extends Controller
{
public function show($slug)
{
// ...
// /blog/my-blog-post
$url = $this->generateUrl(
'blog_show',
array('slug' => 'my-blog-post')
);
}
}
|
Если вам нужно сгенерировать URL из сервиса, типизируйте сервис
UrlGeneratorInterface
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | // src/Service/SomeService.php
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
private $router;
public function __construct(UrlGeneratorInterface $router)
{
$this->router = $router;
}
public function someMethod()
{
$url = $this->router->generate(
'blog_show',
array('slug' => 'my-blog-post')
);
// ...
}
}
|
Генерирование URL со строкой запроса¶
Метод generate()
использует массив значений параметров для генерирования URL.
Но если вы укажете дополнительные параметры, то они будут добавлены в URL как параметры
запроса:
1 2 3 4 5 | $this->router->generate('blog', array(
'page' => 2,
'category' => 'Symfony',
));
// /blog/2?category=Symfony
|
Генерирование URL из шаблона¶
Чтобы сгенерировать URL в Twig, см. статью по шаблонам.
Если вам также надо сгенерировать URL в JavaScript, см Создание URL в JavaScript
.
Генерирование абсолютных URL¶
По умолчанию, маршрутизатор генерирует относительные URL (например,``/blog``).
Чтобы сгенерировать абсолютный URL, укажите UrlGeneratorInterface::ABSOLUTE_URL
в качестве третьего аргумента метода generateUrl()
:
1 2 3 4 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
$this->generateUrl('blog_show', array('slug' => 'my-blog-post'), UrlGeneratorInterface::ABSOLUTE_URL);
// http://www.example.com/blog/my-blog-post
|
Note
Хост, который используется при генерации абсолютного URL, автоматически
определяется при помощи текущего объекта Request
. При генерировании
абсолютных URL вне web-контекста (например, в консольной команде), этот
способ не работает. См. Как генерировать URL из консоли
,
чтобы научиться решать эту проблему.
Устранение проблем¶
Вот некоторые самые распространенные ошибки, с которыми вы можете столкнуться во время работы с маршрутизацией:
# Контроллер "AppBundleControllerBlogController::showAction()" требует, чтобы вы предоставили значение для аргумента "$slug".
Controller "AppBundleControllerBlogController::showAction()" requires that you provide a value for the "$slug" argument.
Это происходит, когда ваш метод контроллера имеет аргумент (например, $slug
):
1 2 3 4 | public function show($slug)
{
// ..
}
|
Но ваш путь маршрута не имеет метасимвола {slug}
(например, он /blog/show
).
Добавьте {slug}
к вашему пути маршрута: /blog/show/{slug}
, либо определите
значение по умолчанию для аргумента (например, $slug = null
).
Некоторым обязательным параметрам не хватает ("slug") для генерирования URL для маршрута "blog_show".
Это означает, что вы пробуете создать URL маршруту blog_show
, но вы не указываете
значение slug
(которое является обязательным, так как оно имеет {slug}
) для
метасимвола в пути маршрута. Чтобы исправить это, укажите значение slug
при генерировани
маршрута:
1 2 3 4 | $this->generateUrl('blog_show', array('slug' => 'slug-value'));
// или, в Twig
// {{ path('blog_show', {'slug': 'slug-value'}) }}
|
Перевод маршрутов¶
Symfony не поддерживает определение маршрутов с разным содержанием, в зависимости от языка пользователя. В таких случаях, вы можете определить несколько маршрутов в контроллере, один для каждого поддерживаемого языка; или использовать любой из пакетов, созданных обществом для реализации этой функции, например, JMSI18nRoutingBundle и BeSimpleI18nRoutingBundle.
Продолжайте!¶
Маршрутизация - готово! Теперь, исследуйте силу контроллеров.
Узнайте больше о маршрутизации¶
- Как ограничить сопоставление маршрутов с помощью условий
- How to Create a custom Route Loader
- Как создать пользовательский загрузчик маршрутов
- Как визуализиовать и отлаживать маршруты
- Как подключать внешние источники маршрутизации
- Как передать дополнительную информацию из маршрута в контроллер
- Как генерировать URL маршрутизации в JavaScript
- Как сделать так, чтобы маршрут соответствовал на основании хоста
- Как определять необязательные заполнители
- Как сконфигурировать перенаправление без пользовательского контроллера
- Перенаправление URL с замыкающим слешем
- Как определять требования маршрутов
- Looking up Routes from a Database: Symfony CMF DynamicRouter
- Поиск маршрутов из базы данных: Symfony CMF DynamicRouter
- Как заставить маршруты всегда использовать HTTPS или HTTP
- Как использовать параметры сервис-контейнера в ваших маршрутах
- Как позволить знак "/" в параметре маршрута
Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.