背景
Laravel Laravel

【Laravel】Controllerのコンストラクタのタイミングでルートパラメータを取得する方法 その2

こんにちは、スズキです。

今回は、以前に紹介したこちらの記事の続編にあたります。


コンストラクタのタイミングでルートパラメータを取得する方法ですが、
以前紹介したように、ルートパラメータ名が判明している場合は
$routeParamName = 'prefecture_slug';
$defaultValue = null;
$routeParam = $router->getCurrentRoute()->getParameter($paramName, $defaultValue)
という形で取得することができます。

ルートパラメータを取得する方法について、基本的には上記方法で問題はありません。
しかし、開発をしていて気がついたのですが、上記方法だと都合の悪いパターンが存在します。
それは以下のように、
Route::resourceを利用したルート構成になっている場合です。
Route::resource(
    'test',
    'TestController',
    ['only' => ['index', 'show']]
);
Route::group(['prefix' => 'test'], function () {
    Route::group(['prefix' => '{id}'], function () {
        Route::put('publish', ['uses' => 'TestController@publish', 'as' => 'test.publish']);
        Route::put('unpublish', ['uses' => 'TestController@unpublish', 'as' => 'test.unpublish']);
    });
});

上記の場合、頭の中では以下のような形で実装を考えていますよね?

●理想
Route::group(['prefix' => 'test'], function () {
    Route::get('/', ['uses' => 'TestController@index', 'as' => 'test.index']);
    Route::get('{id}', ['uses' => 'TestController@show', 'as' => 'test.show']);
    Route::put('{id}/publish', ['uses' => 'TestController@publish', 'as' => 'test.publish']);
    Route::put('{id}/unpublish', ['uses' => 'TestController@unpublish', 'as' => 'test.unpublish']);
});

しかし、実際には以下のようなルート構成になっています。

●現実
Route::group(['prefix' => 'test'], function () {
    Route::get('/', ['uses' => 'TestController@index', 'as' => 'test.index']);
    Route::get('{test}', ['uses' => 'TestController@show', 'as' => 'test.show']);
    Route::put('{id}/publish', ['uses' => 'TestController@publish', 'as' => 'test.publish']);
    Route::put('{id}/unpublish', ['uses' => 'TestController@unpublish', 'as' => 'test.unpublish']);
});

着目すべきはshowのときのルートパラメータ名です。
showのルートパラメータ名が「id」ではなく「test」になっていますよね?
 
つまり、本来は「id」というルートパラメータ名で統一して欲しいところなのに、
Route::resourceを使ってしまうと、URLによってルートパラメータ名が「id」の場合と「test」の場合とに別れることになります。

当然、コンストラクタで共通の処理を記述する際に、「現在のURLを調べて、URLに応じて対象のルートパラメータ名を分岐」なんて実装はあまり良い実装とは言えません。

私も、開発で上記問題に直面した際に困りました。
で、どのような解決策を採用するかですが、私の場合は「何番目のルートパラメータか」を基準にすることで解決することにしました。

つまり、以下のようなメソッドを用意し、
    /**
     * URLに含まれる変数値を取得します。
     * @param int $paramIndex
     * @param null $defaultValue
     * @return object|string
     */
    public static function getRouteParamByIndex($paramIndex = 0, $defaultValue = null)
    {
        $parameters = $this->router->getCurrentRoute()->parameters();
        $currentIndex = 0;
        foreach ($parameters as $param) {
            if ($currentIndex === $paramIndex) {
                return $param;
            }
            $currentIndex++;
        }
        return $defaultValue;
    }

以下のようにルートパラメータを取得することで、ルートパラメータ名に依存せずにルートパラメータ値を取得します。
class TestController extends Controller
{
    /**
     * コンストラクタ
     */
    public function __construct()
    {
        $paramIndex = 0;
        $defaultValue = null;
        $id = CommonUtility::getRouteParamByIndex($paramIndex, $defaultValue);
    }
    
    // 〜〜略〜〜 //
}

これで、ルートパラメータ名が「id」か「test」であるかに関係なく、目的のルートパラメータ値を取得できるようになりました。
 

余談


今回紹介した「何番目のルートパラメータか」という基準でルートパラメータを取得する方法ですが、
例えば、改修作業により以下のような

●改修前
Route::resource(
    'test',
    'TestController',
    ['only' => ['index', 'show']]
);
Route::group(['prefix' => 'test'], function () {
    Route::group(['prefix' => '{id}'], function () {
        Route::put('publish', ['uses' => 'TestController@publish', 'as' => 'test.publish']);
        Route::put('unpublish', ['uses' => 'TestController@unpublish', 'as' => 'test.unpublish']);
    });
});

という内容を

●改修後
Route::group(['prefix' => '{prefecture_slug}'], function () {
    Route::resource(
        'test',
        'TestController',
        ['only' => ['index', 'show']]
    );
    Route::group(['prefix' => 'test'], function () {
        Route::group(['prefix' => '{id}'], function () {
            Route::put('publish', ['uses' => 'TestController@publish', 'as' => 'cnet.test.publish']);
            Route::put('unpublish', ['uses' => 'TestController@unpublish', 'as' => 'cnet.test.unpublish']);
        });
    });
});

という内容に変更するケースを考えます。(※外側に「{prefecture_slug}」が追加されています。)
この場合、当然ですが、ルートパラメータのIndex値基準で取得する方法だと意図した動作をしなくなるため、ルートパラメータ取得部についても修正が必要になります。

ルートパラメータ取得部の修正はルートインデックス値を変更する修正だけなので大した修正量ではないですが、
画面開発者と改修者が異なる場合は気が付きにくい箇所であり、バグの温床になりやすいです。

そのため、今回紹介した方法を利用する場合は、「今後の改修でルート構成が変わってルート変数が増える可能性があるかどうか」は考えた上で利用するようにしたほうが良いと思います。
≪ [PHP] 流行りのPSR-7 ミドルウェアが複雑すぎて人生が辛い  |  【Laravel5】ルーティングを複数ファイルに分けて管理する ≫

Web制作のお問い合わせ

075-744-6842

(平日/土曜 10:00~17:00)

 お問い合わせ