Ground Sunlight

Windowsで作る - PHPプログラミングの開発環境

ユーザ用ツール

サイト用ツール


apricot:usage:ja:controller

差分

このページの2つのバージョン間の差分を表示します。

この比較画面にリンクする

次のリビジョン
前のリビジョン
次のリビジョン 両方とも次のリビジョン
apricot:usage:ja:controller [2020/07/29 13:31]
tanaka 作成
apricot:usage:ja:controller [2020/08/05 12:16]
tanaka [コントローラ]
行 6: 行 6:
  --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-07-29//  --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-07-29//
  
-[[apricot:usage:ja|Apricotの使用法 に戻る]]+[[apricot:usage:ja|Apricot Document に戻る]]
  
 目次 目次
  
 +  * [[apricot:usage:ja:features|Apricot 特徴と概要]]
   * [[apricot:usage:ja:config|Apricot 配置と構成]]   * [[apricot:usage:ja:config|Apricot 配置と構成]]
 +  * [[apricot:usage:ja:errors-logging|Apricot ログとエラー処理]]
   * [[apricot:usage:ja:http|Apricot リクエストとレスポンス]]   * [[apricot:usage:ja:http|Apricot リクエストとレスポンス]]
   * [[apricot:usage:ja:frontend|Apricot フロントエンド]]   * [[apricot:usage:ja:frontend|Apricot フロントエンド]]
行 16: 行 18:
   * [[apricot:usage:ja:middleware|Apricot ミドルウェア]]   * [[apricot:usage:ja:middleware|Apricot ミドルウェア]]
   * Apricot コントローラ   * Apricot コントローラ
-  * [[apricot:usage:ja:errors-logging|Apricot ログとエラー処理]] 
   * [[apricot:usage:ja:utility|Apricot ユーティリティ]]   * [[apricot:usage:ja:utility|Apricot ユーティリティ]]
  
行 22: 行 23:
  
 ===== ルーティングとコントローラ(Auto Wiring) ===== ===== ルーティングとコントローラ(Auto Wiring) =====
->TODO 
  
 +==== ルーティング ====
 +
 +以下のように、config/routes.php にスタブコントローラのルートを追加して下さい。
 +
 +{{fa>folder-open-o}} ** /apricot/config **
 +<code php 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)
 +    {
 +        // Authentication
 +        $r->get ('/login', 'AuthController@showForm');
 +        $r->post('/login', 'AuthController@login');
 +        $r->get ('/logout', 'AuthController@logout');
 +
 +        // User
 +        $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');
 +
 +        // Home
 +        $r->get ('/home', 'HomeController@index');
 +        $r->get('[/]', function() use($base){
 +            header("Location: " . $base.'/home');
 +        });
 +
 +        // Stub
 +        $r->get('/stub[/{no:\d+}]', 'StubController@index');
 +    });
 +};
 +</code>
 +
 +ルーティング設定に関しては[[https://github.com/nikic/FastRoute|FastRoute]]を参照して下さい。
 +
 +\\
 +
 +==== コントローラ ====
 +
 +上述のアプリ用のコントローラベースを継承してスタブコントローラを作ります。
 +
 +{{fa>folder-open-o}} ** /apricot/app/Controllers **
 +<code php StubController.php>
 +<?php
 +namespace App\Controllers;
 +
 +use App\Foundation\Container;
 +use App\Foundation\Controller;
 +
 +/**
 + * Stub Controller
 + */
 +class StubController extends Controller
 +{
 +    /**
 +     * Index Page for this controller.
 +     *
 +     * @return \Apricot\Foundation\Response
 +     */
 +    public function index(int $no=null)
 +    {
 +        $title = "Stub {$no}";
 +
 +        /*
 +         * Example for Container
 +         * @var \App\Models\User $user
 +         */
 +        $user = Container::get('user');
 +        $userCount = count($user->findAll());
 +        $messages[] = "Number of registered users : {$userCount}";
 +
 +        return render('stub',['title'=>$title,'messages'=>$messages]);
 +    }
 +}
 +</code>
 +
 +  * スタブコントローラにindexアクションを実装してる例です
 +  * ボイラープレートのrender()関数を呼び出してレスポンスをレンダリングしています
 +  * **render(string $view=null, array $variables=[])**
 +    - $view : テンプレート名\\ 上例では assets/views/stub.blade.php がテンプレートファイルになります
 +    - $variables : テンプレート変数の連想配列\\ 上例では [$title,$message] をテンプレートに渡しています。
 +
 +\\
 +
 +
 +==== Auto Wiring ====
 +
 +以下は、現状のユーザコントローラのコンストラクタです。
 +
 +<code php>
 +    public function __construct()
 +    {
 +        // モデル
 +        $this->user = new User();
 +        ...
 +    }
 +</code>
 +
 +このコードを ''Auto Wiring'' によって、次のようにすることがここでの目的です。
 +
 +<code php>
 +    public function __construct(User $user)
 +    {
 +        // モデル
 +        $this->user = $user;
 +        ...
 +    }
 +</code>
 +
 +''Auto Wiring'' は、コンストラクタの型ヒントによって生成するオブジェクトを判断します。(オブジェクト型でない引数には対応していません)
 +
 +\\
 \\ \\
  
 ===== インターセプター ===== ===== インターセプター =====
->TODO+ 
 +インターセプター とはアクションの前処理の事です。ミドルウェアと同じでリクエストを中断してレスポンスオブジェクトを生成することもできますが、アクションの後処理はできません。これを図示すると以下のようになります。 
 + 
 +=== インターセプター構造 === 
 + 
 +{{:apricot:usage:ja:controller:ext-fig02.svg?nolink&800}} 
 + 
 +上図から分かるようにミドルウェアパイプラインから見ると、インターセプターはアクションに含まれます。ミドルウェアとの一番の違いは、ミドルウェアは基本的に全てのコントローラを対象としているのに対し、インターセプターは、各コントローラで独自に設定ができるという点です。 
 + 
 +インターセプターの主な用途としては入力データの検証(バリデーション)、入力データのフィルタリングや変換です。インターセプターを作ることで、すっきりしたアクションを作ることができます。 
 + 
 +==== インターセプターの使用 ==== 
 + 
 +インターセプターの登録は、コントローラーのコンストラクタで ''intercept()'' メソッドを使って行います。インターセプターにはクロージャー型とメソッド型の両方が使用できます。簡単なバリデーション処理ならクロジャー型で以下のように書きます。 
 + 
 +<code php> 
 +class FooController extends Controller 
 +
 +    public function __construct() 
 +    { 
 +        // インターセプターの登録 
 +        $this->intercept('action', function(Controller $controller, int $id) 
 +        { 
 +            $inputs = Input::all(); 
 +            ... 
 +        }); 
 +    } 
 +    ... 
 +
 +</code> 
 + 
 +インターセプターに渡される引数は、第1引数に、コントローラのインスタンスが、その後にアクションと同じの引数が続きます。また、インターセプターはレスポンスオブジェクトを返して以降のアクションを中止することができます。 
 + 
 +インターセプターにメソッド型を使用する場合は、以下のように <nowiki>'クラス名@メソッド名'</nowiki> の形式で指定します。 
 + 
 +<code php> 
 +class FooController extends Controller 
 +
 +    public function __construct(User $user) 
 +    { 
 +        // インターセプター登録 
 +        $this->intercept('action1', 'FooInterceptor@action1'); 
 +        $this->intercept('action2', 'FooInterceptor@action2'); 
 +        ... 
 +    } 
 +    .... 
 +
 +</code> 
 + 
 +Apricotでは、インターセプターを配置する場所は以下に決めれられいます。 
 +<code> 
 +aprocot/app/Controllers/Interceptors 
 +</code> 
 + 
 +結果として、インターセプタークラスの名前空間は ''\\App\\Controllers\\Interceptors'' になります。 
 + 
 +自分自身( $this )のメソッドを指定する場合は、''<nowiki>'@メソッド名'</nowiki>'' のように指定します。但し、自分自身のメソッドでも public でないとアクセスできません。
  
 \\ \\
  
 ===== バリデーション ===== ===== バリデーション =====
->TODO+ 
 +Apricotでは Valitron を使いサーバ側のバリデーションで使用します。以下では、Valitron の設定方法について述べます。詳細は [[https://github.com/vlucas/valitron/blob/master/README.md|Valitron の README]] をご覧下さい。 
 + 
 +==== 言語ファイル ==== 
 + 
 +Valitronの言語ファイルを以下の手順でカスタマイズします。 
 + 
 + 
 +  - ''vendor/vlucas/valitron/lang/Valitron/ja.php'' を以下にコピーします 
 +    * ''assets/lang/ja/vlucas.valitron.php'' 
 +  - コピーした ''vlucas.valitron.php'' を以下のように変更します。 
 + 
 +{{fa>folder-open-o}} ** /apricot/assets/lang/ja ** 
 +<code php vlucas.valitron.php> 
 +<?php 
 + 
 +return array( 
 +    'required'      => "を入力してください", 
 +    'equals'        => "は「%s」と同じ内容を入力してください", 
 +    'different'     => "は「%s」と異なる内容を入力してください", 
 +    'accepted'      => "に同意してください", 
 +    'numeric'       => "は数値を入力してください", 
 +    'integer'       => "は半角数字で入力してください", 
 +    'length'        => "は%d文字で入力してください", 
 +    'min'           => "には%sより大きな値を入力してください", 
 +    'max'           => "には%sより小さな値を入力してください", 
 +    'listContains'  => "には選択できない値が含まれています", 
 +    'in'            => "には選択できない値が含まれています", 
 +    'notIn'         => "には選択できない値が含まれています", 
 +    'ip'            => "はIPアドレスの書式として正しくありません", 
 +    'email'         => "の書式が正しくありません", /* 修正 */ 
 +    'url'           => "はURLの書式として正しくありません", 
 +    'urlActive'     => "はアクティブなドメインではありません", 
 +    'alpha'         => "は半角英字で入力してください", 
 +    'alphaNum'      => "は半角英数字で入力してください", 
 +    'slug'          => "は半角英数字、もしくは「-」「_」の文字で入力してください", 
 +    'regex'         => "の書式が正しくありません", 
 +    'date'          => "の書式が正しくありません", /* 修正 */ 
 +    'dateFormat'    => "は「%s」の書式で日付を入力してください", 
 +    'dateBefore'    => "は「%s」以前の日付を入力してください", 
 +    'dateAfter'     => "は「%s」以後の日付を入力してください", 
 +    'contains'      => "は「%s」を含んでいなければいけません", 
 +    'boolean'       => "は真偽値である必要があります", 
 +    'lengthBetween' => "は%d〜%d文字で入力してください", 
 +    'creditCard'    => "はクレジットカード番号の書式として正しくありません", 
 +    'lengthMin'     => "は%d文字以上入力してください", 
 +    'lengthMax'     => "は%d文字以内で入力してください", 
 +    'instanceOf'    => "は「%s」のインスタンスではありません", 
 +    'ascii'         => "は半角文字で入力して下さい", /* 追加 */ 
 +    'unique'        => "は既に使用されています", /* 追加 */ 
 +); 
 +</code> 
 + 
 +  * '''email''' と '''date''' のテキストを変更します 
 +  * '''ascii''' と '''unique''' のテキストを追加します
  
 \\ \\
 +
 +==== uniqueルール ====
 +
 +uniqueルールはデーターベースのユニーク制約の為のルールです。
 +
 +使用法: **rule('unique',$unique_column_name, $table_name, $id_column_name)**
 +
 +  * $unique_column_name --- ユニークカラム名
 +  * $table_name --- テーブル名
 +  * $id_column_name --- IDカラム名
 +
 +<code php>
 +// 使用例
 +$validator = new \Valitron\Validator($inputs);
 +$validator->rule('unique','account', 'user', 'id');
 +</code>
 +
 +\\
 +
 +==== ValidatorErrorBagクラス ====
 +
 +\\
 +
 +==== 使用例:ユーザインターセプター ====
 +
 +インターセプターメソッドのシグネチャには次の規則があります:
 +
 +  * **interceptorMethod( Controller $controller, [, mixed $... ] ):mixed**
 +    - $controller --- インターセプターを呼び出したコントローラ
 +    - $... --- インターセプターを呼び出したアクションメソッドと同じ引数
 +    * 戻り値
 +      - 成功の場合 --- void を返す
 +      - 失敗の場合 --- Responseオブジェクトを返す
 +
 +以下にユーザインターセプター( UserInterceptor )を示します。
 +
 +{{fa>folder-open-o}} ** /apricot/app/Controllers/Interceptors **
 +<code php 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('alphaNum','account')
 +        ->rule('unique','account','user','id')
 +        ->rule('ascii','password')
 +        ->rule('equals','password','password_confirmation')
 +        ->rule('email', 'email')
 +        ->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');
 +    }
 +
 +    /**
 +     * Interceptor for update method.
 +     *
 +     * @param int $id
 +     * @return void|\Apricot\Foundation\Response return Response if failed
 +     */
 +    public function update(Controller $controller, int $id)
 +    {
 +        $inputs = Input::all();
 +
 +        // Validation
 +        $v =(new \Valitron\Validator($inputs))
 +        ->rule('ascii','password')
 +        ->rule('equals','password','password_confirmation')
 +        ->rule('email', 'email')
 +        ->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');
 +    }
 +}
 +</code>
 +
 +バリデーションの手順は以下の通りです。
 +
 +  - Input::all()でフォームデータを取得します
 +  - Validatorの生成
 +    * rule()メソッドで検証ルールを適用します
 +    * labels()メソッドでエラーメッセージで使う項目名を設定します
 +  - Validatorのvalidate()メソッドで検証します
 +    * 検証エラーの時
 +      * withInputs()で入力変数をフラッシュ変数に保存します
 +      * withErrors()でバリデーションのエラーバッグをフラッシュ変数に保存します
 +      * redirect()で前画面にリダイレクトするResponseオブジェクトをします
 +    * 検証成功の時
 +      * 不要になったバリデーション用のフォームデータを削除します
 +
 +=== 検証ルール ===
 +
 +  * **insert()** : レコード挿入時\\ 
 +    * ''required'' --- 必須入力(account, password)
 +    * ''alphaNum'' --- 半角英数入力(account)
 +    * ''unique'' --- ユニーク制約(account)
 +    * ''ascii'' --- 半角入力(password)
 +    * ''equals'' --- 確認入力(password = password_confirmation)
 +    * ''email'' --- メールアドレス(email)
 +
 +  * **update()** : レコード更新時\\ 
 +    * ''ascii'' --- 半角入力(password)
 +    * ''equals'' --- 確認入力(password = password_confirmation)
 +    * ''email'' --- メールアドレス(email)
 +
 +\\
 +
  
 ===== トランザクション ===== ===== トランザクション =====
->TODO+ 
 +ユーザ登録画面にトランザクションの機能を追加します。トランザクションを作るか否かはアクション毎に設定できるようにします。また、トランザクション機能を追加することによりアクションでスローされる ApplicationException をキャッチして(エラー画面に遷移することなく)入力画面でエラーメッセージを表示できるようになります。アクションでスローされるApplicationException には以下のものがあります。 
 + 
 +  * 楽観的ロック例外(OptimissticLockException) 
 +  * 対象レコードが存在しない(レコード更新時および削除時) 
 +  * その他のアクションで発生する''Exception'' \\ ( パースエラーなどの ''Error'' は対象外です ) 
 + 
 +通常、トランザクションは更新系の処理で設定するするので、参照系で発生した全ての例外は集約エラーハンドラで処理されエラー画面が表示されます。この状況を避けたい場合は、次の何れかの選択になるでしょう。 
 + 
 +  * 参照系アクションもトランザクションを設定する 
 +  * アクション内のcatchブロックでエラーメッセージ付きの入力画面をレンダリングする 
 + 
 +\\ 
 + 
 +==== 使用例:ユーザコントローラ ==== 
 + 
 +アクションのトランザクション処理を有効にしたい場合は、コントローラのコンストラクタの中でControllerクラスのtransactional()メソッドを使って、以下のようにします。 
 + 
 +  * **transactional( 'action1', ... );** 
 + 
 +{{fa>folder-open-o}} ** /apricot/app/Controllers ** 
 +<code php 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'); 
 +    } 
 + 
 +    ... 
 +
 +</code> 
 + 
 +  * transactional()を使って3つのアクション( ''insert()'', ''update()'', ''delete()'' )のトランザクション処理が有効になるように設定しています。
  
 \\ \\
  
apricot/usage/ja/controller.txt · 最終更新: 2020/09/03 13:46 by y2sunlight