URLに合わせて動作するプログラムを選ぶ、"Routing"

[Symfony Component Advent Calendar 2022](https://qiita.com/advent-calendar/2022/symfony-component)の2日目の記事です。

## 最初に
[Symfony](https://symfony.com)はPHPのフレームワークのひとつです。しかし、公式サイトの説明文には

> Symfony is a set of PHP Components, a Web Application framework, a Philosophy, and a Community — all working together in harmony.
  （SymfonyはPHPコンポーネントのセットで、Webアプリケーションフレームワークで、哲学、そしてコミュニティです。それらがハーモニーを奏でながら動作しています。）

と書かれている通り、PHPコンポーネントのセットで、たくさんのコンポーネントを提供しており、それらを組み合わせてひとつのフレームワークとして動作しています。Symfonyのコンポーネントは、Symfony上だけで動作するのではなく、他のPHPフレームワークやアプリケーションでも動作している強力なものが揃っています。

今回はそれらの中から、役立ちそうなもの・お薦めしたいものを紹介していきたいと思います。

※記事内ではautoloadのインポートは省略します。


## URLに合わせて動作するプログラムを選ぶ、"Routing"

[Routing](https://symfony.com/doc/current/routing.html)は、リクエストされたURLでどのプログラムを動作させる**設定する**コンポーネントです。設定するだけで、設定されたプログラムを実行しません。Symfony以外でも動作します。

### インストール

```shell
composer require symfony/routing
```

### Symfonyでの設定

Symfonyでの設定はいくつかやり方がありますが、現在推奨されているのは**Attributes**での設定です。


```php
namespace App\Controller;

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

class ItemController extends AbstractController
{
    #[Route('/item', name: 'item_list)]
    public function index(): Response
    {
        // リスト表示処理
    }

    #[Route('/item/{id}', name: 'item_detail')]
    public function detail(int $id): Response
    {
        echo($id); // '/item/1'の場合、1が出力
        // 詳細表示処理
    }
}
```

このように`#[Route('パス', name: 'エイリアス')`という形式でAttributesをそれぞれのアクションメソッドの上部にセットしておけば、勝手に設定・選択してくれます。また、パスに`{id}`のような形でパラメータ指定することで、パスで指定された値をアクションメソッドの同じ名前の変数に渡すことができます。

#### オプション
`name`以外にもいくつかのオプションが用意されています。


```php
#[Route('/item', method: ['GET'])] // メソッドを限定

#[Route('/item/{id}', requirements: ['id' => '\d+'])] // パラメータの値の入力による選別条件を設定（/item/item-nameだと発火しなくなる）

#[Route('/item/{id}', condition: params['id'] < 1000)] // パラメータの値の条件を設定

#[Route('/item/hoge', priority: 2)] // ルーティングの優先順位の設定

#[Route('/item.{_format}', format: 'html', requirements: ['_format' => 'html|json'])] //　フォーマットの指定（デフォルトhtml、htmlとjsonのみ許可）

#[Route('/{_locale}/item', locale: 'ja', requirements: ['_locale' => 'ja|en'])] // ロケールの指定（デフォルトja、jaとenのみ許可）
```

### Symfony以外での設定

Symfony以外で利用する場合は、PHPで設定を記述する必要があります。


#### 設定
```php
use App\Controller\ItemController;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;

$listRoute = new Route('/item', ['_controller' => 'App\Controller\ItemController:index']);
$detailRoute = new Route('/item/{id}', ['_controller' => 'App\Controller\ItemController::detail']);

$routes = new RouteCollection();
$routes->add('item_list', $listRoute);
$routes->add('item_detail', $detailRoute);
```

`Route`クラスで、どのURLに対してどのプログラムを実行するかを設定します。それを`RouteCollection`オブジェクトの`add()`を使って、エイリアス名に紐づけて登録します。

#### 選択

リクエストされたURLから、どのルーティング設定を使うか選択するには、`UrlMatcherを利用します。

```php

use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;

// 上記の設定部分省略

$context = new RequestContext();
$matcher = new UrlMatcher($routes, $context);
$parameters = $matcher->match('/item/1');
print_r($parameters);
```

結果は、

```
Array
(
    [_controller] => App\Controller\ItemController::detail
    [id] => 1
    [_route] => item_detail
)
```

となります。この結果の値をもとにどのプログラムを実行することができます。

## まとめ

今回はルーティングでした。1日目の[Http Foundation](https://qiita.com/ippey_s/private/f9690ba3311505ce0ede)と組み合わせることで、素のPHPファイル内で、リクエスト処理とルーティングまでできるようになります。これだけでも随分とフレームワーク感が出てきますね。