跳到內容

路由元件

編輯此頁

在我們開始深入研究路由元件之前,讓我們先稍微重構目前的框架,使範本更易於閱讀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$map = [
    '/hello' => 'hello',
    '/bye'   => 'bye',
];

$path = $request->getPathInfo();
if (isset($map[$path])) {
    ob_start();
    extract($request->query->all(), EXTR_SKIP);
    include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]);
    $response = new Response(ob_get_clean());
} else {
    $response = new Response('Not Found', 404);
}

$response->send();

由於我們現在提取了請求查詢參數,請依照以下方式簡化 hello.php 範本

1
2
<!-- example.com/src/pages/hello.php -->
Hello <?= htmlspecialchars($name ?? 'World', ENT_QUOTES, 'UTF-8') ?>

現在,我們已準備好新增功能。

任何網站非常重要的一個方面是其 URL 的形式。 借助 URL 對應,我們將 URL 與產生相關回應的程式碼分離,但它還不夠彈性。 例如,我們可能想要支援動態路徑,以允許將資料直接嵌入到 URL 中(例如 /hello/Fabien),而不是依賴查詢字串(例如 /hello?name=Fabien)。

為了支援此功能,請新增 Symfony Routing 元件作為依賴項

1
$ composer require symfony/routing

路由元件不是使用陣列作為 URL 對應,而是依賴 RouteCollection 實例

1
2
3
use Symfony\Component\Routing\RouteCollection;

$routes = new RouteCollection();

讓我們新增一個路由來描述 /hello/SOMETHING URL,並為簡單的 /bye URL 新增另一個路由

1
2
3
4
use Symfony\Component\Routing\Route;

$routes->add('hello', new Route('/hello/{name}', ['name' => 'World']));
$routes->add('bye', new Route('/bye'));

集合中的每個條目都由一個名稱 (hello) 和一個 Route 實例定義,該實例由路由模式 (/hello/{name}) 和路由屬性的預設值陣列 (['name' => 'World']) 定義。

注意

閱讀路由文件,以了解更多關於其眾多功能,例如 URL 生成、屬性需求、HTTP 方法強制執行、YAML 或 XML 檔案的載入器、PHP 或 Apache 重寫規則的轉儲器,以提高效能等等。

根據儲存在 RouteCollection 實例中的資訊,UrlMatcher 實例可以比對 URL 路徑

1
2
3
4
5
6
7
8
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;

$context = new RequestContext();
$context->fromRequest($request);
$matcher = new UrlMatcher($routes, $context);

$attributes = $matcher->match($request->getPathInfo());

match() 方法採用請求路徑並傳回屬性陣列(請注意,比對的路由會自動儲存在特殊的 _route 屬性下)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$matcher->match('/bye');
/* Result:
[
    '_route' => 'bye',
];
*/

$matcher->match('/hello/Fabien');
/* Result:
[
    'name' => 'Fabien',
    '_route' => 'hello',
];
*/

$matcher->match('/hello');
/* Result:
[
    'name' => 'World',
    '_route' => 'hello',
];
*/

注意

即使在我們的範例中並非嚴格需要請求環境,但在實際應用程式中會使用它來強制執行方法需求等等。

當沒有任何路由符合時,URL 比對器會擲回例外

1
2
3
$matcher->match('/not-found');

// throws a Symfony\Component\Routing\Exception\ResourceNotFoundException

記住這些知識,讓我們編寫新版本的框架

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
// example.com/web/front.php
require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;

$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';

$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);

try {
    extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
    ob_start();
    include sprintf(__DIR__.'/../src/pages/%s.php', $_route);

    $response = new Response(ob_get_clean());
} catch (Routing\Exception\ResourceNotFoundException $exception) {
    $response = new Response('Not Found', 404);
} catch (Exception $exception) {
    $response = new Response('An error occurred', 500);
}

$response->send();

程式碼中有一些新事物

  • 路由名稱用於範本名稱;
  • 500 錯誤現在已正確管理;
  • 請求屬性已提取出來,以保持我們的範本簡潔
1
2
// example.com/src/pages/hello.php
Hello <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
  • 路由組態已移至其自己的檔案

    1
    2
    3
    4
    5
    6
    7
    8
    // example.com/src/app.php
    use Symfony\Component\Routing;
    
    $routes = new Routing\RouteCollection();
    $routes->add('hello', new Routing\Route('/hello/{name}', ['name' => 'World']));
    $routes->add('bye', new Routing\Route('/bye'));
    
    return $routes;

我們現在在組態(app.php 中特定於我們應用程式的所有內容)和框架(front.php 中驅動我們應用程式的通用程式碼)之間有了清晰的分隔。

使用不到 30 行程式碼,我們就有了一個新的框架,比以前的框架更強大、更靈活。 盡情享受吧!

使用路由元件還有一個很大的額外好處:能夠根據路由定義產生 URL。 當在程式碼中同時使用 URL 比對和 URL 生成時,變更 URL 模式應該不會有其他影響。 您可以這樣使用產生器

1
2
3
4
5
6
use Symfony\Component\Routing;

$generator = new Routing\Generator\UrlGenerator($routes, $context);

echo $generator->generate('hello', ['name' => 'Fabien']);
// outputs /hello/Fabien

程式碼應該是不言自明的; 並且由於環境的關係,您甚至可以產生絕對 URL

1
2
3
4
5
6
7
8
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

echo $generator->generate(
    'hello',
    ['name' => 'Fabien'],
    UrlGeneratorInterface::ABSOLUTE_URL
);
// outputs something like http://example.com/somewhere/hello/Fabien

提示

擔心效能嗎? 根據您的路由定義,建立一個高度最佳化的 URL 比對器類別,可以取代預設的 UrlMatcher

1
2
3
4
5
6
7
8
use Symfony\Component\Routing\Matcher\CompiledUrlMatcher;
use Symfony\Component\Routing\Matcher\Dumper\CompiledUrlMatcherDumper;

// $compiledRoutes is a plain PHP array that describes all routes in a performant data format
// you can (and should) cache it, typically by exporting it to a PHP file
$compiledRoutes = (new CompiledUrlMatcherDumper($routes))->getCompiledRoutes();

$matcher = new CompiledUrlMatcher($compiledRoutes, $context);
本作品,包括程式碼範例,均根據 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本