メインメニュー
XAMPP アレンジ
IED
WSL2
-
道具箱
リポジトリ編
フレームワーク編
公開ソフトウェア
メタ
リンク
- PHP ライブラリ
- PHP 言語
apricot:usage:ja:controller目次
文書の過去の版を表示しています。
編集中
Apricot コントローラ
— y2sunlight 2020-07-29
目次
- Apricot コントローラ
本章では主にApricotのスケルトンに含まれているユーザコントローラを例にとって説明します。ユーザコントローラは、以下に配置されています。
/your-project/app/Controllers/UserController.php
ルーティング
リクエストルータは、アプリケーションのエンドポイント(URI)がどの コントローラ@アクション を実行するのかを決定します。Apricotでは、リクエストルータに FastRouteを使用しています。ルーティングに関する詳細は FastRoute を参照して下さい。
ルーティングの設定は、
/your-project/config/routes.php
ファイルを編集することで行います。以下はApricotのスケルトンとして提供されているroutes.php
です。/your-project/config
- routes.php
<?php /** * This file contains callback for route definitions. */ return function (FastRoute\RouteCollector $r) { /** @var string $base route base path */ $base = Apricot\Application::getInstance()->getRouteBase(); // Creates a route group with a common prefix. $r->addGroup($base, function (FastRoute\RouteCollector $r) use($base) { // An example $r->get('/home', 'HomeController@index'); }); };
$base
には ApricotのApplication
クラスから取得したアプリケーションのべースパスが保存されています。このパスは、index.php が存在するURIパスに一致します。以下では、$base
を/your-project-path/
と呼びます。$base
をルートに持つルーティンググループをRouteCollector
のaddGroup()
メソッドを使って定義します。これは共通のプレフィックスを持つルートに対して有用な手段です。そして、アプリケーションのルーティングは、このメソッドの第2引数であるクロージャ―の中にコーディングします。上の例では、getメソッドに対する特定のルート(
/your-project-path/home
) とハンドラー(HomeController@index
)を関連付けています。ここでは、ハンドラーにコントローラ名@アクション名
を表す文字列を指定しています。コントローラはクラスで、アクションはメソッドになります。この文字列の解釈はアプリケーションに依存します。これらについては「コントローラとアクション」を参照して下さい。ハンドラーには以下の例のように、クロージャ―を使用するできます。この例では、利用者が
/your-project-path/
をアクセスすると、それは/home
にリダイレクトされます。$r->get('[/]', function() use($base){ header("Location: " . $base.'/home'); });
以下は、ユーザ登録ページの CRUD操作(create, read, update, and delete)に対するルーティングの例です。
<?php $r->get ('/users', 'UserController@index'); $r->get ('/user/create', 'UserController@create'); $r->post('/user/insert', 'UserController@insert'); $r->get ('/user/{id:\d+}/edit', 'UserController@edit'); $r->post('/user/{id:\d+}/update', 'UserController@update'); $r->post('/user/{id:\d+}/delete', 'UserController@delete'); };
フォームから送信される場合はpostメソッドが、それ以外はgetメソッドのルートが定義されています。indexアクションはユーザリスト表示、createアクションは新規登録ページ、editアクションは編集ページを表示します。insert、update そして deleteのアクション はそれぞれCUD操作を実行します。
editアクションなどのルートは名前付きのプレースホルダーを
{名前}:{パターン}
の形式で指定しています。パターンには正規表現が指定できます。この例では、id
と言う名前で1桁以上の数字(\d+
)を指定しています。使用できる正規表現についても FastRouteのREAMME を参照して下さい。
コントローラとアクション
ルーティングによって設定されているエンドポイント(URI)がアクセスされると、Apricotはハンドラー(クロージャでない場合)で指定されている
コントローラ@アクション
をインボークします。コントローラはApp\Foundation\Controller
を継承する必要があります。そして、コントローラは以下の場所に配置する必要があります( 名前空間は\App\Controllers
です )。/your-project/app/Controllers
以下は、Apricotのスケルトンに含まれているHomeコントローラです。このコントローラにはindexアクションだけが含まれています。
/your-project/app/Controllers
- HomeController.php
<?php namespace App\Controllers; use App\Foundation\Controller; use App\Foundation\Security\AuthUser; /** * Home Controller */ class HomeController extends Controller { /** * Index Page for this controller. * * @return \Apricot\Foundation\Response */ public function index() { $message = __('messages.home.msg_hello', [':account'=>AuthUser::getUser()->account]); return render('home',['message'=>$message]); } }
コンストラクター
以下は、ユーザコントローラのコンストラクタです。
/** * User Controller */ class UserController extends Controller { /** * @var \App\Models\User */ private $user; /** * Creates a user controller. */ public function __construct(User $user) { // User Model $this->user = $user; // Registers interceptors. $this->intercept('insert', 'UserInterceptor@insert'); $this->intercept('update', 'UserInterceptor@update'); // Registers transactional actions. $this->transactional('insert','update','delete'); } // ... }
Auto Wiring
public function __construct(User $user)
コントローラのコンストラクタは、Auto Wiring をサポートします。これは、コンストラクター引数の型ヒントを調べることにより、オブジェクトとそのすべての依存関係を再帰的に自動的に解決する機能です。但し、注入できるのはオブジェクト型の変数だけです。Auto Wiring には外部ライブラリーの League/Container を使用しています。
Auto Wiring で使用する引数には、一般的にモデルクラスやサービスクラスを指定します。上の例では、ユーザモデルをコンストラクター引数に指定し、それをメンバ変数(
$this→user
)に格納しています。インターセプターの登録
$this->intercept('insert', 'UserInterceptor@insert'); $this->intercept('update', 'UserInterceptor@update');
コンストラクタではアクションにインターセプターを登録できます。この例では、insertとupdateアクションにそれぞれインターセプターを登録しています。インターセプターについては次項を参照して下さい。
トランザクションの適用
$this->transactional('insert','update','delete');
コンストラクタではアクションにトランザクションを適用できます。この例では、insert、updateそしてdeteleの各アクションにトランザクションを適用しています。トランザクションについては後続の項を参照して下さい。
アクション
以下はユーザコントローラのeditアクションの例です。
public function edit(int $id) { // Finds By the primary key $user = $this->user->findOne($id); if ($user!==false) { return render("user.edit", ["user"=>$user]); } else { return redirect(route("users"))->withOldErrors(); } }
config/routes.php
内のeditアクションのルーティングは次のようになっています。get('/user/{id:\d+}/edit', 'UserController@edit');
例えば、利用者が
/your-project-path/user/1/edit
にアクセスすると UserController の editアクションがインボークされます。この時、edit
のルートパターンである{id:\d+}
は アクションのパラメータint $id
に引き渡され、結果的に$id
には1
になります。パターン中の名前とパラメータの名前は同じでなければなりません。
インターセプター
インターセプター とはアクションの前処理の事です。ミドルウェアと同じでリクエストを中断してレスポンスオブジェクトを生成することもできますが、アクションの後処理はできません。これを図示すると以下のようになります。
インターセプター構造
上図から分かるようにミドルウェアパイプラインから見ると、インターセプターはアクションに含まれます。ミドルウェアとの一番の違いは、ミドルウェアは基本的に全てのコントローラを対象としているのに対し、インターセプターは、各コントローラで独自に設定ができるという点です。
インターセプターの主な用途としては以下のものがあります:
- 入力データの検証(バリデーション)
- 不要な入力データの除去
- 入力データの変換
インターセプターを利用することで、簡潔なアクションを作ることができます。
インターセプターの登録
インターセプターの登録は、コントローラーのコンストラクタの中でコントローラーの
intercept()
メソッドを使って行います。intercept()
の第1引数はアクション名を文字列で、第2引数にはインターセプターを指定します。インターセプターにはクロージャー または 'クラス名@メソッド名' の形式の文字列を指定できます。簡単なバリデーション処理ならクロジャー型で以下のように書きます。class FooController extends Controller { public function __construct() { // インターセプターの登録 $this->intercept('action', function(Controller $controller, int $id) { $inputs = Input::all(); // バリデーション処理 }); } ... }
インターセプターに渡される引数は、第1引数に、コントローラのインスタンスが、その後にアクションと同じの引数が続きます。また、インターセプターはレスポンスオブジェクトを返して以降のアクションを中止することができます。
インターセプターに'クラス名@メソッド名'の文字列を使用した例を以下に示します。
class FooController extends Controller { public function __construct(User $user) { // インターセプター登録 $this->intercept('action1', 'FooInterceptor@action1'); $this->intercept('action2', 'FooInterceptor@action2'); ... } .... }
インターセプターとして自分自身( $this )のメソッドを指定する場合は、
'@メソッド名'
のように指定します。但し、自分自身のメソッドでもインターセプターのアクセス権は public でないといません。
インターセプターの作成
インターセプターは以下の場所に配置する必要があります( 名前空間は
\App\Controllers\Interceptors
です )。/your-project/app/Controllers/Interceptors
インターセプターメソッドのシグネチャには次の規則があります:
/** * Interceptor method * * @param Controller $controller インターセプターを呼び出したコントローラ * @param mixed $... 対応するアクションメソッドと同じ引数 * @return void|\Apricot\Foundation\Response 失敗の場合Responseオブジェクトを返す */ public function interceptorMethod( Controller $controller, [, mixed $... ] ):mixed
以下にユーザコントローラのinsertアクションに対するインターセプター( UserInterceptor@insert )の例を示します。
/your-project/app/Controllers/Interceptors
- UserInterceptor.php
<?php namespace App\Controllers\Interceptors; use Apricot\Input; use App\Foundation\Controller; use App\Foundation\ValidatorErrorBag; /** * User Interceptor */ class UserInterceptor { /** * Interceptor for insert method. * * @return void|\Apricot\Foundation\Response return Response if failed */ public function insert(Controller $controller) { $inputs = Input::all(); // Validation $v =(new \Valitron\Validator($inputs)); ->rule('required', ['account','password']) ->rule('equals','password','password_confirmation') ->labels(inputLabels('messages.user.create')); if(!$v->validate()) { $errorBag = new ValidatorErrorBag($v->errors()); return redirect(back())->withInputs()->withErrors($errorBag); } // Removes unnecessary inputs Input::remove('password_confirmation'); } }
この例では、Input::all() で入力変数を取得した後に、
Validator
のインスタンスを生成し、そのインスタンスのvalidate()
メソッドを使ってバリデーションを実行してます。バリデーションについては次章を参照して下さい。バリデーションが失敗した時、withInputs() で入力変数を、withErrors() でバリデーションのエラーバッグをフラッシュ変数に保存します。そして、redirect()で前画面にリダイレクトするレスポンスオブジェクトを生成し、それを返します。Apricotはインターセプターがレスポンスオブジェクトを返した時、コントローラーアクションを呼び出さずに、そのレスポンスをクライアントに返します。
バリデーションが成功した時、不要になった入力変数 'password_confirmation' を Input::remove() で削除して処理を終了します。その後、Apricotはコントローラーアクションを呼び出します。
トランザクション
ユーザ登録画面にトランザクションの機能を追加します。トランザクションを作るか否かはアクション毎に設定できるようにします。また、トランザクション機能を追加することによりアクションでスローされる ApplicationException をキャッチして(エラー画面に遷移することなく)入力画面でエラーメッセージを表示できるようになります。アクションでスローされるApplicationException には以下のものがあります。
- 楽観的ロック例外(OptimissticLockException)
- 対象レコードが存在しない(レコード更新時および削除時)
- その他のアクションで発生する
Exception
( パースエラーなどのError
は対象外です )
通常、トランザクションは更新系の処理で設定するするので、参照系で発生した全ての例外は集約エラーハンドラで処理されエラー画面が表示されます。この状況を避けたい場合は、次の何れかの選択になるでしょう。
- 参照系アクションもトランザクションを設定する
- アクション内のcatchブロックでエラーメッセージ付きの入力画面をレンダリングする
使用例:ユーザコントローラ
アクションのトランザクション処理を有効にしたい場合は、コントローラのコンストラクタの中でControllerクラスのtransactional()メソッドを使って、以下のようにします。
- transactional( 'action1', … );
/apricot/app/Controllers
- UserController.php
<?php namespace App\Controllers; use App\Exceptions\ApplicationException; use App\Foundation\Controller; use App\Models\User; use Core\Input; /** * ユーザコントローラ */ class UserController extends Controller { ... /** * ユーザコントローラの生成 */ public function __construct() { // モデル $this->user = new User(); // トランザクションアクション登録 $this->transactional('insert','update','delete'); } ... }
- transactional()を使って3つのアクション(
insert()
,update()
,delete()
)のトランザクション処理が有効になるように設定しています。
apricot/usage/ja/controller.1598440587.txt.gz · 最終更新: 2020/08/26 20:16 by y2sunlight
コメント