Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 3 years have 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 の中に含めて送信するだけで十分です。

mpyw
古い記事はそのまま参考にしないようにご注意ください
synapse
Synapseは、オンラインサロンサービスにおけるパイオニアとして、かつて存在していたスタートアップです。
https://synapseam.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away