CakePHPのバリデーションエラーをフロント側で表示する
バックエンド側の準備
Modelの作成
AngularJSからCakePHPのRestful APIにAjaxリクエストを行うCRUDアプリケーションのサンプルを作成した。
本エントリーではそのサンプルアプリにサーバサイドバリデーションを追加して、エラーメッセージをフロント側に表示する部分を作ってみたい(参考 AngularJSからRestfulなリクエストを送信)。
CakePHP側でバリデーションエラーを制御するためModelを作成する。
app\Model\Recipe.php
というファイル名で、以下のようにバリデーションの設定を行う。
class Recipe extends AppModel { public $name = 'Recipe'; public $validate = array( 'title' => array( 'notEmpty' => array( 'rule' => 'notEmpty', 'message' => '題名を記入してください。', 'last' => true ), 'maxLength' => array( 'rule' => array('maxLength', '50'), 'message' => '題名は50文字以内で入力してください。' ), ), 'body' => array( 'notEmpty' => array( 'rule' => 'notEmpty', 'message' => '本文を記入してください。', 'last' => true ), 'maxLength' => array( 'rule' => array('maxLength', '3000'), 'message' => '本文は3000文字以内で入力してください。' ), ), ); }
データ登録APIの開発
Controller側では、
- Modelの読み込み設定
- データ登録用のAPIを追加
という2つの作業が必要。
app\Controller\RecipesController.php
にModelの利用設定を行う。
public $name = 'Recipes'; public $uses = array('Recipe');
AngularJSからHTTPのPOSTメソッドでリクエストされるAPIは以下のようにした。
public function add() { // AngularJSからパラメータとして送信されたデータをCakePHP側でバリデーションしやすいように整形 $data['Recipe']['title'] = $this->params['data']['Recipe']['title']; $data['Recipe']['body'] = $this->params['data']['Recipe']['body']; // バリデーションとデータ追加処理 if ($this->Recipe->save(h($data))) { $message = array( 'text' => __('Saved'), 'type' => 'success' ); $this->set(array( 'message' => $message, '_serialize' => array('message') )); } else { $this->autoRender = false; $this->response->statusCode(400); // バリデーションエラーの場合はHTTP400をレスポンスしてAngularJS側でerrorコールバックを発生させる $error = array( 'text' => __('Error'), 'type' => 'error', 'body' => $this->Recipe->validationErrors // エラーメッセージをフロント側に渡す ); $json = json_encode($error); $this->response->body($json); } }
Controller内でデータを整形
AngularJSからのAjaxリクエストで送信されてくるパラメータは、
$this->params['data']['Recipe']['title']; 1 としてAPI側で扱うことが可能。CakePHPのフォームからsubmitでデータを送信する場合は、 1 $this->request->data['Recipe']['title'];
となるが、Ajaxリクエストの場合はこの点が異なるので注意したい。
バリデーションエラーが発生した場合は、HTTP400のステータスコードを返して、AngularJS側で意図的にerrorコールバックが生じるようにしている。その際に、
$this->Recipe->validationErrors
CakePHPのModelで設定したエラーメッセージなどをまとめて、フロント側に送信する処理を追加してある。
$this->response->body($json);
この処理で送り返されたデータをフロント側でエラーメッセージとして利用する。
フロント側のコールバック処理
errorコールバック内でバックエンドからのレスポンスデータにアクセス
AngularJSサイドのデータ登録用のControllerは以下のようにした。
ajs.controller('NewPostCtrl', function($scope, $rootScope, $http, $location) { $scope.post = {}; $scope.savePost = function() { var _data = {}; _data.Recipe = $scope.recipe; $http.post($rootScope.appUrl + '/recipes.json', _data) .success(function(data, status, headers, config) { $location.path('/posts'); }) .error(function(data, status, headers, config) { console.log(data); console.log(data.body); console.log(data.body.title); $scope.error = data.body; }); } });
NewPostCtrlは、AngularJSからバックエンド側のデータ登録APIに対してリクエストを行うControllerになる。errorコールバックの部分にログ出力の設定をした上で、フォームからtitle無しで送信したのが下の画像。
フロント側にバリデーションのエラーメッセージがレスポンスされていることが確認できる。フォームのエラー表示はcssフレームワークのFoundationで整えた。
部分HTMLでデータを表示
AngularJSのNewPostCtrlに対応する部分HTMLを以下のように組んだ
<div ng-controller="NewPostCtrl"> <h2 class="page-header">New Recipe</h2> <form novalidate class="form-horizontal"> <fieldset> <div class="control-group"> <label class="control-label" for="title">Title <input id="title" type="text" class="input-block-level" required="true" ng-model="recipe.title"></input> </label> <small class="error" ng-show="error.title">{{error.title}}</small> </div> <div class="control-group"> <label class="control-label" for="body">Body</label> <div class="controls"> <textarea id="body" class="input-block-level" rows="10" ng-model="recipe.body" required="true"></textarea> <small class="error" ng-show="error.body">{{error.body}}</small> </div> </div> </fieldset> <div class="form-actions"> <button class="btn btn-primary" ng-click="savePost()"><b class="fa fa-plus-square"></b>Add Recipe</button> </div> </form> </div>
NewPostCtrlのerrorコールバック内で、$scopeにバックエンド側からレスポンスされたエラーメッセージを渡している点にも注目したい。$scopeにエラーを渡すことで上の部分HTMLで
- ng-showを利用したエラーの有無チェック
- エラーメッセージの展開
を行っている。
また、ng-modelを設定し、Ajaxリクエスト時のパラメータデータを構成している点にも注意したい。