やみとものプログラミング日記 やみとものプログラミング日記
TOP テスト駆動開発でルーターを作る Part4
テスト駆動開発でルーターを作る Part4

テスト駆動開発でルーターを作る Part4

プログラミング
作成日時: 2020年7月9日
更新日時: 2020年7月10日
こんにちは、やみともです。
今回は前回「テスト駆動開発でルーターを作る Part3」の続きです。

今回もルーターの機能部分を作っていこうと思います。

今回実装すること

記事ページへのルーティングを実装しようと思います。
前回までと違うのはURLでパラメータを処理する点です。
具体的には次のようなURLでアクセスされた場合に、パラメータとして123を抽出するようにします。

/article/123

では実装・・・、ではなくテストを書いていきましょう。

テスト

[ RouterTest.php ]
class RouterTest extends TestCase
{
    public function test_routing_index()
    {
        Router::get("/", "Index");
        // $controller_class_name = Router::routing("get", "/");    // 変更した
        list($controller_class_name, $params) = Router::routing("get", "/");
        $this->assertEquals("Index", $controller_class_name);
    }

    public function test_routing_contact()
    {
        Router::get("/contact", "Contact");
        // $controller_class_name = Router::routing("get", "/contact");    // 変更した
        list($controller_class_name, $params) = Router::routing("get", "/contact");
        $this->assertEquals("Contact", $controller_class_name);
    }

    public function test_routing_with_params()
    {
        Router::get("/article/[int id]", "Article");
        list($controller_class_name, $params) = Router::routing("get", "/article/123");
        $this->assertEquals("Article", $controller_class_name);
        $this->assertEquals(["id" => 123], $params);
    }
}

テスト結果

$ p
PHPUnit 9.2.5 by Sebastian Bergmann and contributors.

FFF                                                                 3 / 3 (100%)

Time: 00:00.022, Memory: 4.00 MB

There were 3 failures:

1) RouterTest::test_routing_index
Failed asserting that null matches expected 'Index'.

/Users/yuki/testblog/yamitomo_router/tests/RouterTest.php:14

2) RouterTest::test_routing_contact
Failed asserting that null matches expected 'Contact'.

/Users/yuki/testblog/yamitomo_router/tests/RouterTest.php:22

3) RouterTest::test_routing_with_params
Failed asserting that null matches expected 'Article'.

/Users/yuki/testblog/yamitomo_router/tests/RouterTest.php:29

FAILURES!
Tests: 3, Assertions: 3, Failures: 3.

テストが失敗しました。

テストを通す

1つ目と2つ目のテストを通すのは簡単です。

[ Router.php ]
    public static function routing(string $method, string $url)
    {
        // ルーティングテーブルのルートを1つずつ見ていきマッチしたコントローラークラス名を返す
        for ($i = 0; $i < count(self::$routing_table); $i++) {
            $route = self::$routing_table[$i];
            $url_pat = "#^{$route['url']}$#";
            $controller = $route["controller"];

            if (preg_match($url_pat, $url)) {
                return [$controller, null];     // ★変更した
            }
        }
    }

3つ目のテスト

これが難題です。細かい話は後でするとして僕なりに実装してしまいます。

[ Router.php ]
    public static function routing(string $method, string $url)
    {
        // ルーティングテーブルのルートを1つずつ見ていきマッチしたコントローラークラス名を返す
        for ($i = 0; $i < count(self::$routing_table); $i++) {
            $route = self::$routing_table[$i];
            $url_pat = $route["url"];
            $controller = $route["controller"];

            // int型のパラメータ名を抽出列挙する
            preg_match_all("/\[int (?P<ints>[a-zA-Z0-9_]+)\]/", $url_pat, $match);
            $ints = $match["ints"];

            // str型のパラメータ名を抽出列挙する
            preg_match_all("/\[str (?P<strs>[a-zA-Z0-9_]+)\]/", $url_pat, $match);
            $strs = $match["strs"];

            // パターンの作成
            $pat = preg_replace("/\[int ([a-zA-Z0-9_]+)\]/", "(?P<$1>0|[1-9][0-9]*)", $url_pat);
            $pat = preg_replace("/\[str ([a-zA-Z0-9_]+)\]/", "(?P<$1>[a-zA-Z0-9_]+)", $pat);

            if (preg_match("#^{$pat}$#", $url, $match)) {
                $params = [];
                for ($j = 0; $j < count($ints); $j++) {
                    $key = $ints[$j];
                    $params[$key] = intval($match[$key]);
                }
                for ($j = 0; $j < count($strs); $j++) {
                    $key = $strs[$j];
                    $params[$key] = $match[$key];
                }
                $params = $params == [] ? null : $params;

                return [$controller, $params];
            }
        }
    }

Routerクラスのrouting()メソッドを上のように実装しました。
冗長なプログラムになっているかもしれませんが、簡単にやっていることを説明すると、マッチさせるURLの[int id]や[str tag]みたいに指定されているパターンのパターン名(idやtagの部分)を抽出し、実際に値にマッチするURLパターンを生成したのちpreg_matchして各パラメータを抽出しています。

無事、テストも通ったので、今回はここまでとします。

次回予告 & 本紹介

次回以降もルーターライブラリを作っていきます。
ちなみにこれまでの説明もこれからの説明も下のテスト駆動開発の本を参考にしています。
テスト駆動開発に興味のある方は購入されてはどうでしょうか。