全リクエストの処理をルートファイルのクロージャで定義するよりも、コントローラクラスにより組織立てたいと、皆さんも考えるでしょう。関連のあるHTTPリクエストの処理ロジックを一つのクラスへまとめ、グループ分けができます。コントローラはapp/Http/Controllersディレクトリ下に設置します。
コントローラ 7.x Laravel
Laravelでは上記の様にコントローラクラスを用意して各URLが呼ばれた際の動作を定義します。その時、URLの一部を変数の様に見なして処理を定義できます。次の例はURL末尾の数字を id として取り扱う処理です。
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\User;
class UserController extends Controller
{
/**
* 指定ユーザーのプロフィール表示
*
* @param int $id
* @return View
*/
public function show($id)
{
return view('user.profile', ['user' => User::findOrFail($id)]);
}
}
// この id が↑コードの $id に割り当てられる
Route::get('user/{id}', 'UserController@show');
時折この割り当てられたパラメータをコントローラの各処理が呼ばれる前に使いたい時があります。これをコントローラ内だけで実現する方法を紹介します。
実装は次です。
class UserController extends Controller
{
/** @var User */
protected $user;
/**
* コントローラ上でアクションを実行するためのメソッドを上書き.
*
* @param string $method
* @param array $parameters ここにURLと紐づいたパラメータが連想配列で入っている
* @return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
// $parameters から URL で紐づけられた id を呼び出して id を元に User を探す
$this->user = User::findOrFail($parameters['id']);
// ログインしている管理者を取得する
$admin = auth('admin')->user();
// ログインしている管理者は呼び出された User を編集可能な権限があるか確認
if(! (new CheckPermissionService($admin))->canEditUser($this->user)){
abort(401); // 有効な認証資格が不足していることを示すレスポンスコードで処理中断
}
// 編集可能な権限があるならば通常通り親処理を呼び出して実行
return parent::callAction($method, $parameters);
}
/**
* 指定ユーザーのプロフィール表示
*
* @param int $id
* @return View
*/
public function show($id)
{
return view('user.profile', ['user' => $this->user]);
}
/**
* 指定ユーザーの削除
*
* @param int $id
* @return View
*/
public function delete($id)
{
return $this->user->delete();
}
}
// この id が↑コードの $id に割り当てられる
Route::get('user/{id}', 'UserController@show');
Route::delete('user/{id}', 'UserController@delete');
どうしてこの様にできるかというと Laravel 備え付けのルーターが callAction メソッドがあるならば callAction メソッド経由でコントローラのメソッドを呼び出すと定義されているからです。 Laravel 本体のコードは次です。
// Laravel のコントローラ親クラス
abstract class Controller
{
/**
* Execute an action on the controller.
*
* @param string $method
* @param array $parameters
* @return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
// @see https://www.php.net/manual/ja/function.call-user-func-array.php
// call_user_func_array は関数を呼び出す関数.
// 第一引数が呼び出し対象の関数で第二引数がパラメータ.
// 第一引数が[object, string]な配列の場合 object->$string な感じでメソッドを呼ぶ
// つまり
// $userController = new UserController(); // 実際は既にインスタンス化済みの $this
// $id = $parameters['id']
// $userController->show($id)
// の様な処理が実行されている
return call_user_func_array([$this, $method], $parameters);
}
}
// Laravel のルーター配下のコントローラ実行クラス
class ControllerDispatcher implements ControllerDispatcherContract
{
/**
* ルーティング解決のパイプラインの末尾メソッド
* 与えられたコントローラとメソッドをリクエストで実行する
*
* @param \Illuminate\Routing\Route $route ルータ
* @param mixed $controller コントローラのクラスのインスタンス
* @param string $method ルーティングで割り当てたメソッドの名前
* @return mixed
*/
public function dispatch(Route $route, $controller, $method)
{
// @var array $parameters URL に含まれたパラメータを解決して格納
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
// $controller が callAction メソッドを持つならば
if (method_exists($controller, 'callAction')) {
// callAction メソッドを実行
return $controller->callAction($method, $parameters);
}
// $controller が callAction メソッドを持たないならば(実はLaravelの用意したコントローラクラスとまるで違うつくりでも動く)
// 直接メソッドを実行
return $controller->{$method}(...array_values($parameters));
}
}
例に挙げた callAction 中でなんやかんやする利点はコンストラクタ時には未定義の値を使用できる、あるコントローラに依存した処理のためだけにミドルウェア用のクラスやファイルを増やさなくて良い、の二点です。状況をそれなりに選ぶ小技ですが案外使いどころがあるので便利です。