PHP、jQuery、JSON を使用して Ajax サービスを呼び出す

標準化され、集約されたイベント・ベースのシステム

この記事では、Ajax (Asynchronous JavaScript and XML) サービスの呼び出しと、Ajax サービスの呼び出しに対する応答を一貫してイベント・ベースの方法で行うためのシステムについて説明します。このシステムでは、リモート・プロセスの呼び出しの成功、失敗を判断することができます。記事ではこのシステムを使用して、Ajax サービスを呼び出した結果返されるオブジェクトのフォーマットを標準化する方法、Ajax 呼び出しに対してイベント・ベースで応答する方法、そして Ajax による結果の処理を集約する方法を説明します。このシステムは PHP、jQuery、JSON 技術を使用しており、サンプル・コードを見れば、システムの構成がわかります。そして記事の最後で Ajax 呼び出しの例を挙げ、このシステムの各部分のやり取りについて説明して締めくくります。

Jeremy J. Wischusen, Web Application Architect, Binary Neuron L.L.C.

Jeremy Wischusen photoJeremy Wischusen は 13 年以上にわたって Purple Communications、myYearbook.com、HBO などの顧客向けに Web サイトやアプリケーションを設計してきており、Flash、Flex、jQuery、PHP、MySQL、MSSQL、PostgreSQL を使用したフロントエンド・システムとバックエンド・システムの両方を構築してきました。彼は Wyeth Pharmaceuticals、The Vanguard Group、Bucks County Community College、The University of the Arts などの顧客に対し、Web デザイン、Flash、ActionScript を教えた経験があります。



2012年 3月 29日

はじめに

この記事では、PHP、jQuery、JSON を使用して Ajax (Asynchronous JavaScript and XML) サービスの呼び出しと、Ajax サービスの呼び出しに対する応答を一貫してイベント・ベースの方法で行うためのシステムについて説明します。このシステムは、ログインやプロファイルの更新といったサービスに対する Ajax 呼び出しを行うためのものです (HTML ページのようなコンテンツを単にロードするためのものではありません)。このシステムを使用すると、リモート・プロセスの呼び出しが成功したか失敗したかを判断することができます。

よく使われる頭文字語

  • Ajax: Asynchronous JavaScript + XML
  • DOM: Document Object Model
  • HTML: HyperText Markup Language
  • JSON: JavaScript Serialized Object Notation
  • PHP: Hypertext Preprocessor

この記事を読み進めるにあたっては、以下のことを前提としています。

  • JavaScript と PHP の両方で、オブジェクト指向プログラミングの基本を理解していること。
  • DOM レベル 2 のイベント・モデルと、そのモデルを JavaScript で扱う方法を理解していること。サンプルのシステムでは、jQuery ライブラリーを使用してこのモデルを扱います。
  • Ajax の概念の基本を理解していること。
  • JSON による記述を使用してオブジェクトを構成、参照する方法を理解していること。

これらの技術と概念についての詳細は「参考文献」を参照してください。

基礎となる技術

この記事で概説するシステムでは、以下の技術を使用します。

  • PHP の json_encode 関数を使用して PHP 5 サーバーにアクセスする技術 (PHP 5.2.0 またはそれ以降のバージョンの PHP、PECL (PHP Extension Community Library)、そして JSON 1.2.0 またはそれ以降のバージョンの JSON が必要です)
  • jQuery JavaScript ライブラリー (バージョン 1.4.4 またはそれ以降)

このシステムの目標

このシナリオでは、自社の Web サイトの 1 つを再構築することにした企業を取り上げます。この企業は再構築の一環として、Ajax によるポップアップ・ウィンドウを使用してログインとプロファイルの編集を行えるようにしたいと思っています。このサイトは既に改修作業中であったため、この機会をとらえ、Ajax サービスの呼び出しと、それに対する応答を標準化する概念を実装することにしました。

この新しいシステムを作成するに当たっての主な目標は以下の 3 つです。

  • Ajax サービスを呼び出した結果返されるオブジェクトのフォーマットを標準化する
  • Ajax 呼び出しに対してイベント・ベースで応答を返す
  • Ajax による結果の処理を集約する

この記事のこれから先では、このシステムがどのようにしてこれらの目標を実現しているかを概説します。


Ajax サービスを呼び出した結果返されるオブジェクトのフォーマットを標準化する

PHP クラス ServiceResult は、Ajax 呼び出しの結果として作成されるオブジェクトを標準化します。Ajax サービスの呼び出しはすべて、そのように標準化されたオブジェクトを JSON でエンコードして返します。そのため、すべての Ajax サービス呼び出しのインターフェースは一貫性を持った予測可能なものになります。

リスト 1 に PHP の ServiceResult クラスを示します。

リスト 1. PHP の ServiceResult クラス
<?php
    class ServiceResult {

        /**
        *
        * @var Boolean
        */
        public $success;
        /**
        *
        * @var Array
        */
        public $errors;
        /**
        *
        * @var Array
        */
        public $messages;
        /**
        * Holds data returned by a service or passthrough data.
        */
        public $data;
        /**
        *
        * @var String - Event name triggered in JavaScript when service call successful.
        */
        public $onSuccessEvent;
        /**
        *
        * @var String - Event name triggered in JavaScript when service call fails.
        */
        public $onErrorEvent;
    }
?>

このクラスの先頭で、リスト 1 は以下の内容を定義しています。

  • success プロパティー: 呼び出されたプロセスの成功または失敗を示します。このプロパティーは Ajax 呼び出し自体が成功したかどうかを示すものではありません。Ajax 呼び出しが失敗すると JSON オブジェクトを取得することができません。そのため、やはり何らかのフォルト・ハンドラーを使用して Ajax の失敗を処理する必要があります。この例での success は、ログイン・プロセスの間にユーザーが検出されたかどうか、またはユーザーのプロファイルの更新が成功したかどうかを示します。
  • errors プロパティー: その呼び出しが成功しない理由に関する情報を保持する配列です。例えばログイン・プロセスが失敗した場合、この配列には「No user with that user name and password failed (ユーザー名とパスワードが該当するユーザーは存在しないため、失敗しました)」のようなメッセージが含まれているかもしれません。ServiceResult オブジェクトがエラー・メッセージを渡せるようにすることで、メッセージを JavaScript コードでハード・コーディングする必要がありません。JavaScript コードは単純に、この配列の中にユーザーに表示するための何らかのエラー・メッセージが含まれていることを認識する必要があります。
  • messages プロパティー: エラー関連ではないメッセージを結果ハンドラーに渡す配列です。このメッセージは、そのサービスの呼び出しに関連してユーザーが知る必要のある任意の内容が含まれます。例えば、「You have been logged in (既にログイン済みです)」のような成功のメッセージを含めることができます。エラー・メッセージと一般的なメッセージとを別に扱うことで、そのメッセージをユーザーに表示する際、エラー関係のメッセージなのか単なる参考情報なのかを結果ハンドラーで判断する必要がありません (従ってクライアント・サイドのロジックを単純化することができます)。
  • data プロパティー: 任意のデータを結果ハンドラーに渡すための手段です。例えばログイン・サービスを呼び出している場合には data にユーザーのプロファイル・データが含まれているかもしれません。data プロパティー自体は汎用的なものですが、特定のリクエストに対する結果ハンドラーは呼び出されたサービスを基に、このプロパティーにどんなデータが含まれるかを推測する必要があります。いずれにせよ、data プロパティーにより、すべての結果ハンドラーはデータをどこで探せばよいかを把握することができます。
  • onSuccessEvent プロパティーと onErrorEvent プロパティー: サービスの呼び出しを正常に行えた場合と行えなかった場合のそれぞれのケースでブロードキャストされるイベントを定義するストリングです。この場合も Ajax 呼び出し自体が成功したかどうかではなく、呼び出されたプロセスの成功または失敗を示します。これらのイベントには、成功の場合の userLoggedIn や失敗の場合の loginFailed などがあります。

Ajax 呼び出しに対してイベント・ベースで応答を返す

このシステムの 2 番目の目標は、Ajax 呼び出しに対して DOM レベル 2 イベント・モデルを使用してイベント・ベースで応答することです。そのために、この例では JavaScript によるオブジェクト指向の設計を多少使用します。このシステムが機能するためには、個々のオブジェクトはサービスの呼び出しによって返される DOM レベル 2 のイベント (PHP の ServiceResult オブジェクトによって渡される onSuccessEventonErrorEvent イベント) をサブスクライブできなければなりません。すべてのオブジェクトは、イベントをディスパッチする同じオブジェクトをサブスクライブする必要があります。

JavaScript オブジェクトに対し、基本的なオブジェクト継承チェーンを作成します。その際には、使用する JavaScript ライブラリーを切り換える必要が生じた場合でも、JavaScript オブジェクトの DOM レベル 2 イベント・モデルを扱うためのインターフェースに影響を及ぼすことなく切り換えられるような形で、継承チェーンを作成します。

EventDispatcher オブジェクトは継承チェーンの最上位レベルにあり (リスト 2)、DOM レベル 2 イベント・モデルを扱うための JavaScript インターフェースを定義します。またこのオブジェクトにより、すべての JavaScript オブジェクトが同じディスパッチャーをサブスクライブすることが保証されます。

リスト 2. EventDispatcher オブジェクト
function EventDispatcher() {

}

EventDispatcher.prototype.addListener = function (eventName, listener) {
    this.dispatcher.bind(eventName, listener)
}
EventDispatcher.prototype.removeListener = function (eventName, listener) {
    this.dispatcher.unbind(eventName, listener)
}
EventDispatcher.prototype.dispatch = function (eventName, data) {
    this.dispatcher.trigger(eventName, data);
}

EventDispatcher.prototype.dispatcher = jQuery(document);

リスト 2 は以下のようになっています。

  • addListener 関数と removeListener 関数は、このディスパッチャー・オブジェクトのリスナーの追加および削除を行います。
  • eventName パラメーターはサブスクライブされるイベントです。
  • listener パラメーターは登録される関数です。
  • dispatch 関数は eventName パラメーターで指定されたイベントをブロードキャストします。
  • data パラメーターはサブスクライブされる任意のリスナー関数にデータを渡すための手段です。
  • dispatcher プロパティーは文書 DOM オブジェクトを参照する jQuery オブジェクトに設定されます。

dispatcher プロパティーを、文書 DOM オブジェクトを参照する jQuery オブジェクトに設定することで、すべてのオブジェクトのイベントは任意の指定ページに存在する文書 DOM オブジェクトに登録され、またそれらの文書 DOM オブジェクトによってブロードキャストされます。このプロパティーは単純に EventDispatcher オブジェクトのプロパティーであり、すべてのメソッドはこのディスパッチャーのプロパティーを参照します。このディスパッチャーのプロパティーを変更することで、使用するディスパッチャーを変更することができます。単純にディスパッチャーを変更し、そのディスパッチャー・インターフェースに対応するメソッドを更新することで、DOM レベル 2 イベント・モデルにアクセスできる任意のオブジェクトを使用することができます (EventDispatcher オブジェクトを継承するどのオブジェクトにも影響を与えることはありません)。すべてのオブジェクトが同じディスパッチャーをサブスクライブすることが重要です。

次のステップは、EventDispatcher オブジェクトによって定義された関数を個々のスクリプトが継承できるようにすることです。この記事のシナリオの企業には、サイト上の各ページに 1 つのコントローラー・オブジェクトを持ち、そのオブジェクトがそのページの機能を制御する、という規則があります。こうした個々のページのコントローラーと EventDispatcher オブジェクトのギャップを埋めるために、EventDispatcher オブジェクトを継承する BaseController クラスを作成します。

簡単のために、サンプル・コードではイベントのディスパッチとは必ずしも関係しない関数をすべて省略しています。このクラスからコントローラーが継承するものは他にもあります。

リスト 3 は BaseController クラスを示しています。

リスト 3. BaseController クラス
function BaseController() {

}
BaseController.prototype = new EventDispatcher();

BaseController の prototype プロパティーは EventDispatcher オブジェクトに設定されているため、ページ専用のコントローラーが BaseController を継承すると、そのコントローラーにはイベント・ディスパッチャー関数が含まれるようになります。


Ajax による結果の処理を集約する

このシステムの最後の目標は、Ajax サービスを呼び出した結果の処理を集約することです。そのためには jQuery の ajaxComplete 関数を使用します。この関数は jQuery を使用する Ajax 呼び出しが完了すると自動的に呼び出されます。必ずそうなるように、リスト 4 のコードをスクリプト・ファイルに追加し、Ajax サービスを呼び出すすべてのページにそのスクリプト・ファイルを含めます。

リスト 4. jQuery の ajaxComplete 関数
jQuery(document).ajaxComplete(function(event, request, settings) {
    if (settings.dataType == 'json') {
        var dispatcher = new EventDispatcher();
        var result = jQuery.parseJSON(request.responseText);
        if (result) {
            if (result.success == true && result.onSuccessEvent) {
                dispatcher.dispatch(result.onSuccessEvent, result)
            } else if (result.onErrorEvent) {
                dispatcher.dispatch(result.onErrorEvent, result)
            }
        }
    }
})

文書オブジェクトにハンドラーが登録されていますが、これはすべてのページに文書オブジェクトがあることがわかっているからです。この関数は呼び出されるたびに、以下のオブジェクトが渡されます。

  • jQuery の Ajax イベント・オブジェクト (event パラメーター)
  • XMLHttpRequest (request パラメーター)
  • 元々の Ajax 呼び出しに使用された設定を表すオブジェクト (settings パラメーター)

このサンプル・システムで関心の対象となるのは、XMLHttpRequest オブジェクトと settings オブジェクトです。

リスト 4 のハンドラーの中のコードを詳しく見てみましょう。このハンドラーは最初に settings オブジェクトを調べ、結果のフォーマットが JSON に設定されているかどうかを確認します。(このシステムの目的が単なるコンテンツ・ロード・リクエストに応答することではなく、サービス・ベースの Ajax 呼び出しに応答することであることを思い出してください。それらの Ajax 呼び出しへの応答はすべて、JSON でエンコードされた ServiceResult が返されます。) Ajax リクエストへの応答として返されたデータのタイプが JSON でない場合には、そのデータには関心がないため、ハンドラーはそのデータを処理するすべてのコードをバイパスします。

データのタイプが JSON の場合には、このコードは JavaScript の EventDispatcher のインスタンスを作成します。EventDispatcher クラスのインスタンスはすべて同じディスパッチャー・インスタンスを参照するため、そのディスパッチャー・インスタンスによってブロードキャストされる任意のイベントによって、そのディスパッチャー・インスタンスに (EventDispatcher クラスを継承する任意の JavaScript オブジェクトによって) 登録されているすべてのリスナーがトリガーされます。

次にこのコードは、リクエスト・オブジェクトの responseText プロパティーから JSON でエンコードされたデータへの参照を (jQuery の parseJSON 関数を使用して) 取得し、result 変数に格納します。

responseText の構文解析が成功した場合、ハンドラーは result オブジェクトの success プロパティーを調べることで、呼び出したプロセスが成功したことを result オブジェクトが示しているかどうかを確認します。成功の場合には、ハンドラーは onSuccessEvent が定義されているかどうかを確認し、定義されている場合には、event ディスパッチャー・インスタンスがそのイベントを result オブジェクトと共に再びブロードキャストします。

呼び出したプロセスの成功を result オブジェクトが示していない場合には、このコードは onErrorEvent を調べます。onErrorEvent が定義されている場合には、dispatcher インスタンスがそのイベントを再びブロードキャストし、この場合もブロードキャストの際に result オブジェクトを渡します。

jQuery の ajaxComplete 関数を使用することで、イベントに応じて応答するようにクライアント・サイドのコードに通知することができます。Ajax 呼び出しのリスナーとしてクライアント・サイドのオブジェクトを登録する必要はありません。クライアント・サイドのコードはサービスの呼び出しが行われていることを認識する必要すらありません。クライアント・サイドのコードが認識しておかなければならないことは、どんなイベントをリッスンするのか、また返されたデータにどう対応すればよいのかということのみです。


Ajax 呼び出しの例

このセクションでは、Ajax による単純なログイン・サービスを呼び出す例を取り上げ、このシステムの各部分がどのようにやり取りするのかを説明します。簡単のため、必要なすべての PHP ファイルと JavaScript ファイルが必要な場所に含まれているものとします。

呼び出されるサービスを表す PHP ファイルを作成し、JSON でエンコードされた ServiceResult オブジェクトが返されるようにします。この PHP ファイルの一例をリスト 5 に示します。

リスト 5. PHP ファイル
<?php
    $ctrl = new LoginController();
    $result = new ServiceResult();
    $result->success = $ctrl->login($_POST['username'], $_POST['password']);
    $result->errors = $ctrl->errors;
    $result->onSuccessEvent = 'refreshPage';
    $result->onErrorEvent = 'userLoginFailed';
    echo json_encode($result);
?>

上記ファイルに加え、このサービスを呼び出す JavaScript オブジェクトが必要です。この例では HTML のログイン・フォームを既に作成してあるものとします。このサービスを呼び出すオブジェクトを LoginController と呼ぶことにします (リスト 6)。

リスト 6. LoginController でのサービスの呼び出し
LoginController.prototype.login = function() {
    var self = this;
    var username = jQuery('#loginForm .email').val()
    var password = jQuery('#loginForm .password').val()
    var data = {
        username:username,
        password:password,
    };
    jQuery.ajax({
        url:'login.php',
        async:true,
        service:'login',
        type:'post',
        dataType:'json',
        data:data
    });
}

このシステムはイベントを使用して Ajax サービスの呼び出しに応答するため、jQuery.ajax には結果ハンドラーとフォルト・ハンドラーは渡していません。

これでサービス呼び出しの部分が作成できたので、ログイン・サービスによって返される ServiceResult オブジェクト内に定義される onSucessEventsonErrorEvents に対し、オブジェクトが応答する必要があります。応答するオブジェクトは EventDispatcher を継承する必要があります。そのためには BaseController を継承します。この場合、LoginController オブジェクトはログインに失敗したことを示すメッセージをユーザーに対して表示します。ログインに成功した場合には、AppController オブジェクトがそのページを最新の状態に更新することによって応答します。まず、リスト 7 の LoginController から説明を始めます。

リスト 7. LoginController にリスナーを追加する
function LoginController() {
    var self = this;
    jQuery(document).ready(function() {
        self.onLoad();
    })
}

LoginController.prototype = new BaseController();

LoginController.prototype.onLoad = function() {
    var self = this;
    this.addListener('userLoginFailed', function(event, result) {
        self.onLoginFailed(result);
    })
}

LoginController.prototype.onLoginFailed = function(result) {
    jQuery('#errorDisplay').html(result.errors[0]);
}

上記コードでは、コンストラクターで onLoad 関数を呼び出しています。この例では onLoad 関数の呼び出しは重要ではありませんが、これを含めている理由は、このコントローラーが参照する必要のある DOM オブジェクトはページがロードされるまで利用できない可能性があるためです。

ここで注意が必要な重要項目は以下の 2 つです。

  • LoginController のプロトタイプ・オブジェクトは BaseController です。従って LoginControllerEventDispatcher メソッドを継承します。
  • onLoad 関数では EventDispatcheraddListener メソッドを使用しています。このメソッドでは渡されるイベントと、ログイン・サービスが返す ServiceResult オブジェクトによって返される onErrorEvent との突き合わせが行われます。登録されているハンドラー関数には、result オブジェクト (ログイン・サービスによって返される ServiceResult オブジェクトを JSON でエンコードしたもの) が渡されます。

onLoginFailed メソッドは result オブジェクトのエラー配列を使用してユーザーにメッセージを表示します。これがログインに失敗した場合の処理です。

ユーザーがログインに成功した場合、ユーザーのセッション・データが更新されるように、ページを最新の状態に更新する必要があります。ただし、ページを最新の状態に更新することで、サービスの呼び出しに応答するとよいケースは他にもあります (例えばユーザーが自分のプロファイルを更新する場合など)。そして、ページを更新するための結果ハンドラーを持つコントローラーを複数用意する代わりに、この機能を上位レベルのコントローラーに持たせるようにします。すると、セッション・データを更新するためにページを最新の状態に更新することを、サービスの呼び出しが必要としている場合、単に refreshPage イベント・タイプを onSucessEvent としてディスパッチすることによって、サービスの呼び出しはページを更新することができます。そのためには、LoginController で使用したプロセスを AppController で繰り返します (リスト 8)。

リスト 8. AppController オブジェクト
function AppController() {
    var self = this;
    jQuery(document).ready(function() {
        self.onLoad();
    })
}

AppController.prototype = new BaseController();

AppController.prototype.onLoad = function () {
    this.addListener('refreshPage', function(event, user) {
        window.location.reload()
    })
}

この場合も、AppController は単純にプロトタイプを BaseController のインスタンスに設定し、addListener を使用して refreshPage イベントのリスナー関数を登録します。

これで、応答オブジェクトを作成してイベント・リスナーを登録できたので、スクリプトに onAjaxComplete イベント・ハンドラー関数を含めます。サービスの呼び出しが完了すると、onAjaxComplete ハンドラーは登録されたハンドラー関数を通知する適切なイベントをディスパッチします。

LoginControllerAppController は互いを認識する必要はなく、どちらのコントローラーもサービスの呼び出しに応答していることさえ認識しないので、同じサービスの呼び出しに応答していることを認識することは当然ありません。


まとめ

この記事で検討した Ajax サービス呼び出しシステムは以下の特徴を備えています。

  • Ajax サービスを呼び出した結果返されるオブジェクトのフォーマットは標準化してあり、(JSON でエンコードされたフォーマットで返される) 標準化された PHP オブジェクト ServiceResult を使用しています。
  • Ajax 呼び出しに対してイベント・ベースで応答を返します。そのために、JavaScript オブジェクト EventDispatcher を設計し、このオブジェクトがすべてのサブクラスを登録して、共通のディスパッチャーでのイベントに応答するようにしました。
  • Ajax 呼び出しの結果の処理が集約されています。そのために jQuery の onAjaxComplete ハンドラーを集約ハンドラーとして動作させ、サービスの呼び出しによって返される ServiceResult オブジェクトに指定されているイベントをブロードキャストしました。

そして Ajax 呼び出しの例として、互いに相手を認識しない 2 つの JavaScript オブジェクトを作成し、同じサービス呼び出しに応答させる方法を説明しました。

参考文献

学ぶために

製品や技術を入手するために

  • jQuery JavaScript ライブラリー: バージョン 1.4.4 またはそれ以降をダウンロードしてください。
  • IBM 製品の評価版: IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2、Lotus、Rational、Tivoli、WebSphere などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=806650
ArticleTitle=PHP、jQuery、JSON を使用して Ajax サービスを呼び出す
publish-date=03292012