Laravel5でAPIを作成するとき、個人的に気になっていたことは、
- CSRFの回避
- FormRequestの利用
の2つです。
1.はLaravelでは、POSTは原則tokenが要求されるので、それをどうしたものか?という悩みです。Middlewareで実装されているので外してしまうのは簡単なのですが、一方でCSRFのリスクは増します。Laravel5.1.xからは、一部のルートを除外できる機能が付いたのでそれを試します。
2.FormRequestは、Laravel5.xから採用された機能で、バリデーション等をFormRequestに記述することでコントローラーが非常にスッキリしていいのですが、エラー時の対応などが自動化(隠蔽)されており、API時にどう利用したらよいか、ずっと気になっていたので動作を確認してみます。
##csrfの回避(except)
Laravel5.1.xから、exceptで、除外したいrouteを記述すればよいことになりました。
例えば、http//hostname/apiを除外したい場合、Http/Middleware/VerifyCsrfToken.phpに、
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
class VerifyCsrfToken extends BaseVerifier
{
protected $except = [
'api'
];
}
と記述すればよいようになりました。'api/*'のようにワイルドカードも使えるようです。
試したところ、tokenが要求されることがなくなりました。
##FormRequestを利用したValidation
以前からのValidationでは、明示的にif($validation->fails())等で、処理を分岐し、また、分岐後の処理も明示的に記述していたいので(逆に言えば、記述しなければならない)特に問題はなかったのですが、FormRequestは、そのあたりが隠蔽されているので、動作のチェックをします。
まず、通常のバリデーション。
###通常のValidation
public function store(Request $request)
{
//バリデーション対象
$inputs = $request->all();
//ルール
$rules = [
'name'=>'required',
'email'=>'required|email',
];
//バリデーション
$validation = \Validator::make($inputs,$rules);
//外部からの処理を受ける(jquery用)
$headers = [
'Access-Control-Allow-Origin' =>' *',
];
//評価
//エラーの時
if($validation->fails())
{
$response["status"] = "NG";
return \Response::json($response,'200',$headers);
}
//正常の時
$response["status"] = "OK";
return \Response::json($response,'200',$headers);
}
まあ、何をしているかわかりやすいといえばわかりやすいです。
なお、ここではクロスドメインでのリクエストを許可するために、ヘッダに、Access-Control-Allow-Orignを追加し、どのドメインからのリクエストも許可しています。
###FormRequest
FormRequestを利用すると、少なくともコントローラー部は一気にシンプルになります。
下記が、同じ部分のコントローラーです。
public function store(\App\Http\Requests\PostRequest $request)
{
$response["status"] = "OK";
return \Response::json($response,'200',$headers);
}
ここでは、\App\Http\Requests\PostRequestを定義し、インジェクションしています。
記述はシンプルなのですが、バリデーション等が、いつ、どこで行われ、その結果どうなるのかが全く???となります。特に、異常時の処理は一切記述されない(できない)ため、???となります。
通常のフォームで利用した場合、エラー時には、自動的に各種パラメーターを付与した状態で、リクエスト元にリダイレクトされます(リダイレクト元では、パラメータを利用してエラー処理とかをします)。
つまり、
return redirect()->back()->withErrors()->withInput();
が実行されています。
要は、APIでの利用の場合、これがどうなるのか?ということです。
結論から言えば、RequestFormのresponse()メソッドをオーバーライドして必要な記述をすれば良いようです。
FormRequest自体は、artisanコマンドで生成できます。
php artisan make:request PostRequest
必要な内容を記述します。通常は、
- authorizeの戻り値をtrueに。
- rulesを定義
だけですが、ここでは、response()を定義(オーバーライド)し、エラー時の処理を記述しています。
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class PostRequest extends Request
{
//認証(これは基本trueにしてやる)
public function authorize()
{
return true;
}
//ルール
public function rules()
{
return [
'name'=>'required',
'email'=>'required|email',
];
}
//メッセージも書けます
//エラー時の処理
public function response(array $errors)
{
$headers = [
'Access-Control-Allow-Origin' =>' *',
];
$response["status"] = "NG+";
$response["message"] = $errors;
return \Response::json($response,200,$headers);
}
}
これで完了です。
ちなみに、上記の記述だと、下記のようなレスポンスが得られます。
{"status":"NG+","message":{"name":["The name field is required."],"email":["The email must be a valid email address."]}}
メッセージは、フォームの時と同じ内容となります。
RequestFormのソースコードを見る限りでは、ajaxやheaderの内容を見て自動的にjsonを返すようになっているようですが、その状態でそのまま使えることはないかなという感じなので、カスタマイズの方法を調べてみました。
##メモ
ajaxのテストに利用したクライアント側のコードも参考までに(一部環境依存部あり)。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>test</title>
</head>
<body>
<input type="button" name="button" value="push" id="btn">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
$(function(){
$("#btn").click(function(){
$.ajax({
type:"post",
url:"http://localhost:8000/api",
data:{"name":"hoge","email":"hoge@hoge.com"},
success:function(data){
alert(data.status);
}
});
});
});
</script>
</body>
</html>