Symfony4中文文檔: 路由

路由

漂亮的URL是任何嚴謹的Web應用程序所必須的. 這意味着像 index.php?article_id=57 這樣醜陋的URL要被 /read/intro-to-symfony 所取代.javascript

具備靈活性更加劇要. 若是你須要將 /blog 更改成 /news , 須要作些什麼? 你須要搜索並更新多少連接才能作出這種改動? 若是你使用的是Symfony的路由, 更改將是很簡單的.php

建立路由

路由是從URL到控制器的映射, 假如你想要一個路由徹底匹配 /blog 和另外更多可匹配任何像 /blog/my-post/blog/all-about-symfony URL的動態路由.html

路由能夠在YAML, XML和PHP. 全部格式都提供相同的功能和性能, 所以可選擇你喜歡的格式. 若是你選擇PHP annotations, 請在你的應用程序中運行一次此命令以添加對它們的支持:java

$ composer require annotations

如今你能夠配置路由:web

Annotations正則表達式

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

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

class BlogController extends AbstractController
{
    /**
     * Matches /blog exactly
     *
     * @Route("/blog", name="blog_list")
     */
    public function list()
    {
        // ...
    }

    /**
     * Matches /blog/*
     *
     * @Route("/blog/{slug}", name="blog_show")
     */
    public function show($slug)
    {
        // $slug will equal the dynamic part of the URL
        // e.g. at /blog/yay-routing, then $slug='yay-routing'

        // ...
    }
}

YAMLjson

# config/routes.yaml
blog_list:
    path:     /blog
    controller: App\Controller\BlogController::list

blog_show:
    path:     /blog/{slug}
    controller: App\Controller\BlogController::show

XMLapi

<!-- 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數組

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog', array(
    '_controller' => [BlogController::class, 'list']
)));
$routes->add('blog_show', new Route('/blog/{slug}', array(
    '_controller' => [BlogController::class, 'show']
)));

return $routes;

感謝這兩條路由:app

  • 若是用戶訪問 /blog , 匹配第一條路由配置而且 list() 將被執行;
  • 若是用戶訪問 /blog/* , 匹配第二條路由配置而且 show() 將被執行. 由於路由路徑是 /blog/{slug}, 因此 $slug 變量傳遞給該值匹配的 show() . 例如, 若是用戶訪問 /blog/yay-routing , 那麼 $slug 將等於 yay-routing .

每當路由路徑中有 {placeholder} 時, 該部分就成爲通配符: 它將匹配任意值. 你的控制器如今也有一個名爲 $placeholder 的參數 ( 通配符和參數名稱必須匹配 ).

每一個路由還有一個內部名稱: blog_listblog_show . 這些能夠是任意內容 ( 只要每一個都是惟一的 ) 而且須要無任何特別含義. 稍後你將使用它們來生成URL.

其餘格式的路由

每一個方法上面的 @Route 稱爲 annotation. 若是你更願意使用YAML, XML或PHP配置路由, 那沒問題! 只需建立一個新的路由文件 ( 例如 routes.xml ) , Symfony就會自動使用它.

本地化路由(i18n)

路由能夠本地化地爲每一個區域提供惟一的路徑. Symfony提供了一種簡便的方式來聲明本地化路由而無重複.

Annotations

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

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

class CompanyController extends AbstractController
{
    /**
     * @Route({
     *     "nl": "/over-ons",
     *     "en": "/about-us"
     * }, name="about_us")
     */
    public function about()
    {
        // ...
    }
}

YAML

# config/routes.yaml
about_us:
    path:
        nl: /over-ons
        en: /about-us
    controller: App\Controller\CompanyController::about

XML

<!-- 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="about_us" controller="App\Controller\CompanyController::about">
        <path locale="nl">/over-ons</path>
        <path locale="en">/about-us</path>
    </route>
</routes>

PHP

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

return function (RoutingConfigurator $routes) {
    $routes->add('about_us', ['nl' => '/over-ons', 'en' => '/about-us'])
        ->controller('App\Controller\CompanyController::about');
};

當本地化路由匹配時, Symfony會自動識別請求期間應使用哪一個區域的路由設置. 以這種方式定義路由避免了對路由重複註冊的須要, 最小化了由定義不一致引發的任何錯誤的風險.

爲全部路由添加前綴是國際化應用程序的一個常見需求. 這樣能夠經過爲每一個語言環境定義不一樣的路徑前綴來完成 ( 若是願意, 能夠爲默認語言設置一個空前綴 ):

YAML

# config/routes/annotations.yaml
controllers:
    resource: '../../src/Controller/'
    type: annotation
    prefix:
        en: '' # don't prefix URLs for English, the default locale
        nl: '/nl'

添加 {通配符} 條件

想象一下, blog_list 路由將包含一個博客主題的分頁列表, 其中包含 /blog/2/blog/3 等第2頁和第3頁的URL. 若是你將路徑修改成 /blog/{page} , 你將會遇到一個問題:

  • blog_list: /blog/{page} 將匹配 /blog/*;
  • blog_show: /blog/{slug} 將仍然匹配 /blog/*;

當兩條路由匹配相同的URL時, 加載的第一條路由將勝利. 不幸的是, 這意味着 /blog/yay-routing 將匹配 blog_list.

要解決此問題, 添加一個 {page} 通配符用來只匹配數字:

Annotations

// 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($page)
    {
        // ...
    }

    /**
     * @Route("/blog/{slug}", name="blog_show")
     */
    public function show($slug)
    {
        // ...
    }
}

YAML

# config/routes.yaml
blog_list:
    path:      /blog/{page}
    controller: App\Controller\BlogController::list
    requirements:
        page: '\d+'

blog_show:
    # ...

XML

<!-- 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

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page}', array(
    '_controller' => [BlogController::class, 'list'],
), array(
    'page' => '\d+'
)));

// ...

return $routes;

\d+ 是一個匹配任意長度數字的正則表達式. 如今:

URL Route Parameters
/blog/2 blog_list $page = 2
/blog/yay-routing blog_show $slug = yay-routing

若是你願意, 能夠在每一個佔位符中使用語法 {placeholder_name<requirements>} . 此功能使配置更簡潔, 但當需求複雜時, 它會下降路由可讀性:

Annotations

// 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($page)
    {
        // ...
    }
}

YAML

# config/routes.yaml
blog_list:
    path:      /blog/{page<\d+>}
    controller: App\Controller\BlogController::list

XML

<!-- 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<\d+>}"
           controller="App\Controller\BlogController::list" />

    <!-- ... -->
</routes>

PHP

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page<\d+>}', array(
    '_controller' => [BlogController::class, 'list'],
)));

// ...

return $routes;

要了解其餘路由條件 ( 如HTTP方法, 主機名和動態表達式 ) 請參閱 How to Define Route Requirements

給{佔位符}一個默認值

在前面的例子中, blog_list 的路徑爲 /blog/{page} . 若是用戶訪問 /blog/1 , 則會匹配. 若是用戶訪問 /blog , 將沒法匹配. 只要向路由路徑添加了 {佔位符} , 它就必須有值.

那麼當用戶訪問 /blog 時, 如何讓 blog_list 再次匹配呢? 經過添加一個 默認 值:

Annotations

// 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($page = 1)
    {
        // ...
    }
}

YAML

# config/routes.yaml
blog_list:
    path:      /blog/{page}
    controller: App\Controller\BlogController::list
    defaults:
        page: 1
    requirements:
        page: '\d+'

blog_show:
    # ...

XML

<!-- 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

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route(
    '/blog/{page}',
    array(
        '_controller' => [BlogController::class, 'list'],
        'page'        => 1,
    ),
    array(
        'page' => '\d+'
    )
));

// ...

return $routes;

如今, 當用戶訪問 /blog 時, blog_list 路由會匹配, 而且 $page 路由參數會默認取值爲 1 .

與{通配符}條件同樣, 使用語法 {placeholder_name?default_value} 也能夠在每一個佔位符中內聯默認值. 此功能與內聯條件兼容, 所以你能夠在一個佔位符中內聯:

Annotations

// 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($page)
    {
        // ...
    }
}

YAML

# config/routes.yaml
blog_list:
    path:      /blog/{page<\d+>?1}
    controller: App\Controller\BlogController::list

XML

<!-- 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 <\d+>?1}"
           controller="App\Controller\BlogController::list" />

    <!-- ... -->
</routes>

PHP

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/{page<\d+>?1}', array(
    '_controller' => [BlogController::class, 'list'],
)));

// ...

return $routes;
佔位符變量的值如果 null 變量, 則須要在通配符最後添加 ? 字符. ( 例如 /blog/{page?} ) .

所有路由列表

隨着你應用程序的健壯, 最終會有大量的路由被定義! 要查看全部內容, 請運行命令:

$ php bin/console debug:router

------------------------------ -------- -------------------------------------
 Name                           Method   Path
------------------------------ -------- -------------------------------------
 app_lucky_number                 ANY    /lucky/number/{max}
 ...
------------------------------ -------- -------------------------------------

高級路由示例

請查看高級示例:

Annotations

// src/Controller/ArticleController.php

// ...
class ArticleController extends AbstractController
{
    /**
     * @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

# 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

<!-- 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

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\ArticleController;

$routes = new RouteCollection();
$routes->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 $routes;

如你所見, 只有當URL的 {_locale} 部分爲 enfr{year} 爲數字時, 此路由纔會匹配. 示例還展現瞭如何在佔位符之間使用 . 號來替換 / . 如下URL均可匹配:

  • /articles/en/2010/my-post
  • /articles/fr/2010/my-post.rss
  • /articles/en/2013/my-latest-post.html
_format 路由參數

示例突出顯示了 _format 特殊路由參數, 當使用此參數時, 匹配的值將成爲Request對象的"請求格式".

最後, 請求格式被用做設置返回 Content-Type 之類的事情 ( 例如: 一個JSON請求格式會轉換 Content-Typeapplication/json )

特殊路由參數

如你所見, 每一個路由參數或默認值最終均可以做爲控制器方法的參數. 此外, 還有四個特殊參數: 每一個參數在應用程序中具備獨特的功能:

_controller

   用於肯定路由匹配時執行的控制器

_format

   用於設置請求格式 ( 閱讀更多 )

_fragment

   用於設置fragment identifier, URL的最後可選部分, 以 # 字符開頭, 用於標識文檔的某一部分.

_locale

   用於在請求上設置區域 ( 閱讀更多 )

尾部斜槓重定向URL

從歷史上看, URL遵循UNIX約定, 即爲路徑添加尾部斜槓 ( 例如 https://example.com/foo/ ) , 當刪除斜槓時將做爲文件引用 ( https://example.com/foo ) . 雖然爲兩個URL提供不一樣的內容是能夠的, 但如今將兩個URL視爲相同的URL並在他們之間重定向是很常見的.

Symfony遵循這個邏輯, 在帶斜槓和不帶斜槓的URL之間重定向 ( 但僅限於GET和HEAD請求 ):

Route path If the requested URL is /foo If the requested URL is /foo/
/foo It matches (200 status response) It makes a 301 redirect to /foo
/foo/ It makes a 301 redirect to /foo/ It matches (200 status response)
若是你的應用程序爲每一個路徑 ( /foo/foo/ ) 定義了不一樣的路由, 則不會發生自動重定向, 而且始終匹配正確的路由.

在Symfony4.1中引入了從 /foo//foo 的自動301重定向. 在以前的Symfony版本中, 會響應404.

控制器命名模式

路由中的控制器格式很是簡單 CONTROLLER_CLASS::METHOD .

To refer to an action that is implemented as the __invoke() method of a controller class, you do not have to pass the method name, but can just use the fully qualified class name (e.g. AppControllerBlogController).

生成URL

路由系統也能夠生成URL. 實際上, 路由是雙向系統: 將URL映射到控制器以及路由返解爲URL.

要生成URL, 你須要制定路由的名稱 ( 例如 blog_show ) 以及該路由的路徑中使用的任何通配符 ( 例如 slug = my-blog-post ) . 有了這些信息, 可輕鬆生成任何URL:

class MainController extends AbstractController
{
    public function show($slug)
    {
        // ...

        // /blog/my-blog-post
        $url = $this->generateUrl(
            'blog_show',
            array('slug' => 'my-blog-post')
        );
    }
}

若是須要從服務生成URL, 注入 UrlGeneratorInterface 服務.

// 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() 方法採用通配符數組來生成URI. 可是若是你傳遞額外值, 他們將做爲查詢字符串添加到URI中.

$this->router->generate('blog', array(
    'page' => 2,
    'category' => 'Symfony',
));
// /blog/2?category=Symfony

生成本地化URL

路由本地化時, Symfony默認使用當前請求區域來生成URL. 爲了生成不一樣語言環境的URL, 你必須在parameters數組中傳遞 _locale :

$this->router->generate('about_us', array(
    '_locale' => 'nl',
));
// generates: /over-ons

從模板中生成URL

要在Twig中生成URL: 請參閱模板章節. 若是你須要在JavaScript中生成URL, 請參閱 How to Generate Routing URLs in JavaScript

生成絕對URL

默認狀況下, 路由將生成相對URL ( 例如 /blog ) . 在控制器中, 將 UrlGeneratorInterface::ABSOLUTE_URL 傳遞給 generateUrl() 方法的第三個參數:

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
The host that's used when generating an absolute URL is automatically detected using the current Request object. When generating absolute URLs from outside the web context (for instance in a console command) this doesn't work. See How to Generate URLs from the Console to learn how to solve this problem.

錯誤排除

如下是使用路由時可能會遇到的一些常見錯誤:

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

當你的控制器方法有一個參數 ( 例如 $slug ) 時會發生這種狀況:

public function show($slug)
{
    // ..
}

你的路由沒有 {slug} 通配符 ( 例如 /blog/show ). 在你的路由路徑中增長 {slug} : /blog/show/{slug} 或爲參數設置一個默認值 ( 例如 $slug = null )

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

這意味着你正在嘗試生成 blog_show 路由的URL, 但你沒有傳遞 slug 值 (這是必須的, 由於在路由路徑中有一個 {slug} 通配符). 要解決此問題, 請在生成路由時傳遞 slug 值:

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

// or, in Twig
// {{ path('blog_show', {'slug': 'slug-value'}) }}
相關文章
相關標籤/搜索