PHP
JavaScript
jQuery
セキュリティ
真夏の夜の淫夢

JavaScript(jQuery)からPHPのAPIを利用する

More than 1 year has passed since last update.


はじめに

類似の記事はありますが、個人的に納得のいくものが無かったので書いてみます。

SS


基本形


HTML

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
<title>サンプル</title>
</head>
<body>
<h1>設定</h1>
<p>
年齢: <input type="text" id="age" value="24"><br>
職業: <input type="text" id="job" value="学生"><br>
<input type="button" id="execute" value="送信"><br>
</p>
<h1>結果</h1>
<p>
可否: <input type="text" id="result" value=""><br>
内容: <input type="text" id="detail" value=""><br>
</p>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="default.js"></script>
</body>
</html>


JavaScript


手抜き


default.js

// DOMを全て読み込んだあとに実行される

$(function () {
// 「#execute」をクリックしたとき
$('#execute').click(function () {
// Ajax通信を開始する
$.ajax({
url: 'api.php',
type: 'post', // getかpostを指定(デフォルトは前者)
dataType: 'json', // 「json」を指定するとresponseがJSONとしてパースされたオブジェクトになる
data: { // 送信データを指定(getの場合は自動的にurlの後ろにクエリとして付加される)
age: $('#age').val(),
job: $('#job').val(),
},
})
// ・ステータスコードは正常で、dataTypeで定義したようにパース出来たとき
.done(function (response) {
$('#result').val('成功');
$('#detail').val(response.data);
})
// ・サーバからステータスコード400以上が返ってきたとき
// ・ステータスコードは正常だが、dataTypeで定義したようにパース出来なかったとき
// ・通信に失敗したとき
.fail(function () {
$('#result').val('失敗');
$('#detail').val('');
});
});
});


失敗時の情報も丁寧に表示する


default.js

/* エラー文字列の生成 */

function errorHandler(args) {
var error;
// errorThrownはHTTP通信に成功したときだけ空文字列以外の値が定義される
if (args[2]) {
try {
// JSONとしてパースが成功し、且つ {"error":"..."} という構造であったとき
// (undefinedが代入されるのを防ぐためにtoStringメソッドを使用)
error = JSON.parse(args[0].responseText).error.toString();
} catch (e) {
// パースに失敗した、もしくは期待する構造でなかったとき
// (PHP側にエラーがあったときにもデバッグしやすいようにレスポンスをテキストとして返す)
error = 'parsererror(' + args[2] + '): ' + args[0].responseText;
}
} else {
// 通信に失敗したとき
error = args[1] + '(HTTP request failed)';
}
return error;
}

// DOMを全て読み込んだあとに実行される
$(function () {
// 「#execute」をクリックしたとき
$('#execute').click(function () {
// Ajax通信を開始する
$.ajax({
url: 'api.php',
type: 'post', // getかpostを指定(デフォルトは前者)
dataType: 'json', // 「json」を指定するとresponseがJSONとしてパースされたオブジェクトになる
data: { // 送信データを指定(getの場合は自動的にurlの後ろにクエリとして付加される)
age: $('#age').val(),
job: $('#job').val(),
},
})
// ・ステータスコードは正常で、dataTypeで定義したようにパース出来たとき
.done(function (response) {
$('#result').val('成功');
$('#detail').val(response.data);
})
// ・サーバからステータスコード400以上が返ってきたとき
// ・ステータスコードは正常だが、dataTypeで定義したようにパース出来なかったとき
// ・通信に失敗したとき
.fail(function () {
// jqXHR, textStatus, errorThrown と書くのは長いので、argumentsでまとめて渡す
// (PHPのfunc_get_args関数の返り値のようなもの)
$('#result').val('失敗');
$('#detail').val(errorHandler(arguments));
});
});
});



PHP


手抜き


api.php

<?php

// Content-TypeをJSONに指定する
header('Content-Type: application/json');

// 「200 OK」 で {"data":"24歳、学生です"} のように返す
$data = "{$_POST['age']}歳、{$_POST['job']}です";
echo json_encode(compact('data'));



エラーの発生を防ぎ、値の整合性チェックも行う


api.php

<?php

// Content-TypeをJSONに指定する
header('Content-Type: application/json');

// $_POST['age']、$_POST['job']をエラーを出さないように文字列として安全に展開する
$age = (string)filter_input(INPUT_POST, 'age');
$job = (string)filter_input(INPUT_POST, 'job');

// 整合性チェック
if ($age === '' || $job === '') {
$error = '年齢と職業を両方入力してください';
} else if (!ctype_digit($age)) {
$error = '年齢は正の整数で入力してください';
} else if (($age = (int)$age) > 100) {
$error = '生きすぎィ!';
}

if (!isset($error)) {
// 正常時は 「200 OK」 で {"data":"24歳、学生です"} のように返す
$data = "{$age}歳、{$job}です";
echo json_encode(compact('data'));
} else {
// 失敗時は 「400 Bad Request」 で {"error":"..."} のように返す
http_response_code(400);
echo json_encode(compact('error'));
}



FAQ


Q. done とか fail みたいに繋ぐ書き方なんなの?

私が知ってるのはこういう書き方だったはずなんだけど…

$.ajax({

url: 'api.php',
type: 'post',
dataType: 'json',
success: function (data) {
$('#result').val('成功');
$('#detail').val(response.data);
},
error: function () {
$('#result').val('失敗');
$('#detail').val(errorTextFromFailArguments(arguments));
},
});


A. Promise インタフェースを利用したものです

そのような書き方をすると、レスポンスの結果をもとに新たにAjaxリクエストを実行したいときなどに、いわゆる「コールバックネスト地獄」に陥ります。そういったシーンのための解決策として、この書き方がjQuery1.5以降から推奨されているようです。詳しくは以下を参照してください。

jQuery日本語リファレンスjQuery1.4で更新が止まっているのであまり参考にしない方がいいでしょう。

なお,より標準のPromiseに近い形で利用する場合,done fail でバラバラに記述しているものを then でまとめたほうがよいでしょう.

.then(

function (response) {
$('#result').val('成功');
$('#detail').val(response.data);
},
function () {
$('#result').val('失敗');
$('#detail').val('');
}
);



Q. data のセットが面倒くさい!

数が増えてた時にどうにかならんの?

var data = {

age: $('#age').val(),
job: $('#job').val(),
};


A. FormData オブジェクトを利用しましょう

以下のように name 属性を使って送信項目を定義しておけば、FormData オブジェクトを渡すだけで済みます。

<form id="settings">

年齢: <input type="text" name="age" value="24"><br>
職業: <input type="text" name="job" value="学生"><br>
<input type="button" id="execute" value="送信"><br>
</p>

var data = new FormData($('#settings')[0]);

後から項目を追加したい場合は append メソッドが使えます。

var data = new FormData($('#settings')[0]);

data.append('drink', 'アイスティー');



Q. クロスオリジンでやりたい!

このままだと

XMLHttpRequest cannot load .... No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '...' is therefore not allowed access. 

みたいなエラーが出る…


A. Access-Control-Allow-OriginJSONP を利用しましょう


Access-Control-Allow-Origin を利用する場合

PHP側で以下のようにレスポンスヘッダを返します。


example.comからの利用を許可する場合

header('Access-Control-Allow-Origin: http://example.com');



全ての利用を許可する場合

header('Access-Control-Allow-Origin: *');



JSONP を利用する場合

PHP側で以下のようにレスポンスボディを返します。

$data = "{$age}歳、{$job}です";

echo 'callback(' . json_encode(compact('data')) . ')';

JavaScript側では以下のように書けば、JSONのときと全く同じように処理することが可能です。内部で何やってるか分からなくて気持ち悪いという人は「jQueryはJSONPの理解の妨げになるか?」を参照してください。

$.ajax({

url: 'api.php',
type: 'post',
dataType: 'jsonp',
jsonpCallback: 'callback',
data: {
age: $('#age').val(),
job: $('#job').val(),
},
})


セキュリティに関する注意点


XSS対策

ユーザ入力をエレメントに適用する際にはjQueryの text メソッドや val メソッドを通すことを徹底しましょう。htmlメソッドは危険です。


CSRF対策

通常のフォーム送信と同じように実装しましょう。同一オリジンでは何もしなくてもセッションIDはCookieとして送信されるので、単にPHPから返されてきたトークンを data の中に含めて送信するだけで十分です。