The Routing Component

Before we start diving into the Routing component, let's refactor our current framework just a little to make templates even more readable:

Routing コンポーネントに飛び込む前に、現在のフレームワークを少しだけリファクタリングして、テンプレートをさらに読みやすくしましょう。
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();

As we now extract the request query parameters, simplify the hello.php template as follows:

リクエスト クエリ パラメータを抽出するので、hello.phptemplate を次のように単純化します。
1
2
<!-- example.com/src/pages/hello.php -->
Hello <?= htmlspecialchars(isset($name) ? $name : 'World', ENT_QUOTES, 'UTF-8') ?>

Now, we are in good shape to add new features.

これで、新しい機能を追加できる状態になりました。

One very important aspect of any website is the form of its URLs. Thanks to the URL map, we have decoupled the URL from the code that generates the associated response, but it is not yet flexible enough. For instance, we might want to support dynamic paths to allow embedding data directly into the URL (e.g. /hello/Fabien) instead of relying on a query string (e.g. /hello?name=Fabien).

Web サイトの非常に重要な側面の 1 つは、その URL の形式です。 URL マップのおかげで、関連する応答を生成するコードから URL を切り離しましたが、まだ十分な柔軟性がありません。たとえば、動的パスをサポートして、クエリ文字列 (例: /hello?name=Fabien) に頼るのではなく、データを URL (例: /hello/Fabien) に直接埋め込むことができるようにしたい場合があります。

To support this feature, add the Symfony Routing component as a dependency:

この機能をサポートするには、Symfony ルーティング コンポーネントを依存関係として追加します。
1
$ composer require symfony/routing

Instead of an array for the URL map, the Routing component relies on a RouteCollection instance:

URL マップの配列の代わりに、Routing コンポーネントは RouteCollection インスタンスに依存します。
1
2
3
use Symfony\Component\Routing\RouteCollection;

$routes = new RouteCollection();

Let's add a route that describes the /hello/SOMETHING URL and add another one for the simple /bye one:

/hello/SOMETHING URL を記述するルートを追加し、単純な /bye 用に別のルートを追加しましょう。
1
2
3
4
use Symfony\Component\Routing\Route;

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

Each entry in the collection is defined by a name (hello) and a Route instance, which is defined by a route pattern (/hello/{name}) and an array of default values for route attributes (['name' => 'World']).

コレクション内の各エントリは、名前 (hello) と、ルート パターン (/hello/{name}) およびルート属性のデフォルト値の配列 (['name' => 'World') によって定義される Routeinstance によって定義されます。 ]))。

Note

ノート

Read the Routing documentation to learn more about its many features like URL generation, attribute requirements, HTTP method enforcement, loaders for YAML or XML files, dumpers to PHP or Apache rewrite rules for enhanced performance and much more.

ルーティングのドキュメントを読んで、URL 生成、属性要件、HTTP メソッドの適用、YAML または XML ファイルのローダー、パフォーマンスを強化するための PHP または Apache 書き換えルールへのダンパーなどの多くの機能について学習してください。

Based on the information stored in the RouteCollection instance, a UrlMatcher instance can match URL paths:

RouteCollection インスタンスに格納されている情報に基づいて、aUrlMatcher インスタンスは 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());

The match() method takes a request path and returns an array of attributes (notice that the matched route is automatically stored under the special _route attribute):

match() メソッドはリクエスト パスを受け取り、属性の配列を返します (一致したルートは自動的に special_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',
];
*/

Note

ノート

Even if we don't strictly need the request context in our examples, it is used in real-world applications to enforce method requirements and more.

この例ではリクエスト コンテキストが厳密に必要なわけではありませんが、実際のアプリケーションではメソッド要件などを強制するために使用されます。

The URL matcher throws an exception when none of the routes match:

どのルートも一致しない場合、URL マッチャーは例外をスローします。
1
2
3
$matcher->match('/not-found');

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

With this knowledge in mind, let's write the new version of our framework:

この知識を念頭に置いて、フレームワークの新しいバージョンを書きましょう。
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();

There are a few new things in the code:

コードにはいくつかの新しい機能があります。
  • Route names are used for template names;
    ルート名はテンプレート名に使用されます。
  • 500 errors are now managed correctly;
    500 エラーが正しく管理されるようになりました。
  • Request attributes are extracted to keep our templates simple:
    テンプレートをシンプルに保つために、リクエスト属性が抽出されます。
1
2
// example.com/src/pages/hello.php
Hello <?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
  • Route configuration has been moved to its own file:

    ルート構成は独自のファイルに移動されました:
    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;

We now have a clear separation between the configuration (everything specific to our application in app.php) and the framework (the generic code that powers our application in front.php).

これで、構成 (app.php 内のアプリケーション固有のすべて) とフレームワーク (front.php 内のアプリケーションを強化する汎用コード) が明確に分離されました。

With less than 30 lines of code, we have a new framework, more powerful and more flexible than the previous one. Enjoy!

30 行未満のコードで、以前のフレームワークよりも強力で柔軟な新しいフレームワークができました。楽しみ!

Using the Routing component has one big additional benefit: the ability to generate URLs based on Route definitions. When using both URL matching and URL generation in your code, changing the URL patterns should have no other impact. You can use the generator this way:

ルーティング コンポーネントを使用すると、大きな利点がもう 1 つあります。それは、ルート定義に基づいて 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

The code should be self-explanatory; and thanks to the context, you can even generate absolute URLs:

コードは自明である必要があります。コンテキストのおかげで、絶対 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

Tip

ヒント

Concerned about performance? Based on your route definitions, create a highly optimized URL matcher class that can replace the default UrlMatcher:

パフォーマンスが気になる?ルート定義に基づいて、defaultUrlMatcher を置き換えることができる高度に最適化された URL マッチャー クラスを作成します。
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);