36.3. Zend_XmlRpc_Server

36.3.1. 導入

Zend_XmlRpc_Server は、完全な機能を有した XML-RPC サーバです。 www.xmlrpc.com で提示されている仕様 に準拠しています。 さらに system.multicall() メソッドを実装しており、 リクエストをまとめる (boxcarring of requests) ことができます。

36.3.2. 基本的な使用法

もっとも基本的な使用例は次のとおりです。

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'My/Service/Class.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('My_Service_Class');
echo $server->handle();

36.3.3. サーバの構造

Zend_XmlRpc_Server はさまざまなコンポーネントで構成されています。 サーバ自身からリクエスト、レスポンス、fault オブジェクトなど広範囲に広がっています。

Zend_XmlRpc_Server を起動するには、 まずサーバにひとつ以上のクラスか関数をアタッチする必要があります。 アタッチするには setClass() メソッドおよび addFunction() メソッドを使用します。

起動させたら、次に Zend_XmlRpc_Request オブジェクトを Zend_XmlRpc_Server::handle() に渡します。 もし渡さなかった場合は、Zend_XmlRpc_Request_Http のインスタンスを作成して php://input からの入力を受け取ります。

Zend_XmlRpc_Server::handle() は、 リクエストメソッドに応じて適切なハンドラに処理を振り分けます。 そして、 Zend_XmlRpc_Response を継承したオブジェクトか Zend_XmlRpc_Server_Fault オブジェクトを返します。 これらのオブジェクトはどちらも __toString() メソッドを実装しており、妥当な XML-RPC XML レスポンスを直接出力することができます。

36.3.4. 規約

Zend_XmlRpc_Server では、開発者が関数やクラスメソッドを XML-RPC メソッドとしてアタッチできるようになっています。 アタッチされるメソッドの情報は Zend_Server_Reflection を使用して取得し、関数やメソッドのコメントブロックから メソッドのヘルプ文とシグネチャを取得します。

XML-RPC の型は必ずしも PHP の型と一対一対応しているわけではありません。 しかし、@param や @return の行をもとに、できるだけ適切な型を推測しようとします。 XML-RPC の型の中には、直接対応する PHP の型がないものもありますが、 その場合は phpdoc の中で XML-RPC の型のヒントを指定します。 たとえば次のような型が該当します。

  • dateTime.iso8601 ... YYYYMMDDTHH:mm:ss 形式の文字列

  • base64 ... base64 エンコードされたデータ

  • struct ... 任意の連想配列

ヒントを指定するには、次のようにします。

<?php
/**
* これはサンプル関数です
*
* @param base64 $val1 Base64 エンコードされたデータ
* @param dateTime.iso8601 $val2 ISO 日付
* @param struct $val3 連想配列
* @return struct
*/
function myFunc($val1, $val2, $val3)
{
}

PhpDocumentor はパラメータや返り値の型を検証しません。 そのため、これが API ドキュメントに影響を及ぼすことはありません。 しかし、このヒントは必須です。メソッドがコールされた際に、 この情報をもとにサーバで検証を行うからです。

パラメータや返り値で複数の型を指定してもかまいません。 XML-RPC の仕様では、system.methodSignature は すべてのメソッドシグネチャ (すなわちパラメータと返り値の組み合わせ) の配列を返すことになっています。 複数指定する方法は、通常の PhpDocumentor の場合と同様に '|' 演算子を使用します。

<?php
/**
* This is a sample function
*
* @param string|base64 $val1 文字列あるいは base64 エンコードされたデータ
* @param string|dateTime.iso8601 $val2 文字列あるいは ISO 日付
* @param array|struct $val3 Normal 数値添字配列あるいは連想配列
* @return boolean|struct
*/
function myFunc($val1, $val2, $val3)
{
}

しかし、注意すべきことがあります。複数のシグネチャを定義すると、 それを利用する開発者を混乱させてしまいます。 一般論として、XML-RPC のメソッドは複数のシグネチャを持たないほうがいいでしょう。

36.3.5. 名前空間の活用

XML-RPC には名前空間の概念があります。基本的に、これは 複数の XML-RPC メソッドをドット区切りの名前空間でまとめるものです。 これにより、さまざまなクラスで提供されるメソッド名の衝突を避けることができます。 例として、XML-RPC サーバは 'system' 名前空間でこれらのメソッドを提供することが期待されています。

  • system.listMethods

  • system.methodHelp

  • system.methodSignature

内部的には、これらは Zend_XmlRpc_Server の同名のメソッドに対応しています。

自分が提供するメソッドに名前空間を追加したい場合は、 関数やクラスをアタッチする際のメソッドで名前空間を指定します。

<?php
// My_Service_Class のパブリックメソッドは、すべて
// myservice.メソッド名 でアクセスできるようになります
$server->setClass('My_Service_Class', 'myservice');

// 関数 'somefunc' は funcs.somefunc としてアクセスするようにします
$server->addFunction('somefunc', 'funcs');

36.3.6. 独自のリクエストオブジェクト

ほとんどの場合は、 Zend_XmlRpc_Server や Zend_XmlRpc_Request_Http に含まれるデフォルトのリクエスト型を使用するでしょう。 しかし、XML-RPC を CLI や GUI 環境などで動かしたい場合もあるでしょうし、 リクエストの内容をログに記録したい場合もあるでしょう。 そのような場合には、Zend_XmlRpc_Request を継承した独自のリクエストオブジェクトを作成します。 注意すべき点は、getMethod() メソッドと getParams() メソッドを必ず実装しなければならないということです。 これらは、XML-RPC サーバがリクエストを処理する際に必要となります。

36.3.7. 独自のレスポンス

リクエストオブジェクトと同様、Zend_XmlRpc_Server は独自のレスポンスオブジェクトを返すこともできます。 デフォルトでは Zend_XmlRpc_Response_Http オブジェクトが返されます。 これは、XML-RPC で使用される適切な Content-Type HTTP ヘッダを送信します。独自のオブジェクトを使用する場面としては、 レスポンスをログに記録したり、 あるいはレスポンスを標準出力に返したりといったことが考えられます。

独自のレスポンスクラスを使用するには、handle() をコールする前に Zend_XmlRpc_Server::setResponseClass() を使用します。

36.3.8. Fault による例外の処理

Zend_XmlRpc_Server は、配送先のメソッドで発生した例外を捕捉します。 例外を捕捉した場合は、XML-RPC の fault レスポンスを生成します。 しかし、デフォルトでは、例外メッセージとコードは fault レスポンスで用いられません。これは、 あなたのコードを守るための判断によるものです。 たいていの例外は、コードや環境に関する情報を必要以上にさらけ出してしまいます (わかりやすい例だと、データベースの抽象化レイヤの例外を想像してみてください)。

しかし、例外クラスをホワイトリストに登録することで、 fault レスポンス内で例外を使用することもできます。 そうするには、 Zend_XmlRpc_Server_Fault::attachFaultException() を使用して例外クラスをホワイトリストに渡します。

<?php
Zend_XmlRpc_Server_Fault::attachFaultException('My_Project_Exception');

他のプロジェクトの例外を継承した例外クラスを利用するのなら、 一連のクラス群を一度にホワイトリストに登録することもできます。 Zend_XmlRpc_Server_Exceptions は常にホワイトリストに登録されており、 固有の内部エラー (メソッドが未定義であるなど) を報告することができます。

ホワイトリストに登録されていない例外が発生した場合は、 コード '404'、メッセージ 'Unknown error' の falut レスポンスを生成します。

36.3.9. リクエスト間でのサーバ定義のキャッシュ

たくさんのクラスを XML-RPC サーバインスタンスにアタッチすると、 リソースを大量に消費してしまいます。各クラスを調べるために リフレクション API を (Zend_Server_Reflection 経由で) 使用する必要があり、 使用できるすべてのメソッドのシグネチャをサーバクラスに提供します。

使用するリソースの量を軽減するために、Zend_XmlRpc_Server_Cache を用いてリクエスト間でサーバ定義をキャッシュすることができます。 __autoload() と組み合わせることで、これはパフォーマンスを劇的に向上させます。

使用例は次のようになります。

<?php
require_once 'Zend/Loader.php';
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Cache.php';

function __autoload($class)
{
    Zend_Loader::loadClass($class);
}

$cacheFile = dirname(__FILE__) . '/xmlrpc.cache';
$server = new Zend_XmlRpc_Server();

if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
    require_once 'My/Services/Glue.php';
    require_once 'My/Services/Paste.php';
    require_once 'My/Services/Tape.php';

    $server->setClass('My_Services_Glue', 'glue');   // glue. 名前空間
    $server->setClass('My_Services_Paste', 'paste'); // paste. 名前空間
    $server->setClass('My_Services_Tape', 'tape');   // tape. 名前空間

    Zend_XmlRpc_Server_Cache::save($cacheFile, $server);
}

echo $server->handle();

この例では、スクリプトと同じディレクトリにある xmlrpc.cache からサーバの定義を取得しようとします。取得できなかった場合は、 必要なサービスクラスを読み込み、 それをサーバのインスタンスにアタッチし、 そしてその定義を新しいキャッシュファイルに記録します。

36.3.10. 使用例

以下のいくつかの使用例で、開発者が使用できるオプションを説明します。 各使用例は、それまでに紹介した例に追加していく形式になります。

36.3.10.1. 基本的な使用法

次の例は関数を XML-RPC メソッドとしてアタッチし、 受け取ったコールを処理します。

<?php
require_once 'Zend/XmlRpc/Server.php';

/**
 * 値の MD5 sum を返します
 *
 * @param string $value md5sum を計算する値
 * @return string 値の MD5 sum
 */
function md5Value($value)
{
    return md5($value);
}

$server = new Zend_XmlRpc_Server();
$server->addFunction('md5Value');
echo $server->handle();

36.3.10.2. クラスのアタッチ

次の例は、クラスのパブリックメソッドを XML-RPC メソッドとしてアタッチします。

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Services/Comb.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb');
echo $server->handle();

36.3.10.3. 名前空間を用いた複数のクラスのアタッチ

次の例は、複数のクラスをそれぞれの名前空間でアタッチします。

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // メソッドをコールするには comb.* とします
$server->setClass('Services_Brush', 'brush'); // メソッドをコールするには brush.* とします
$server->setClass('Services_Pick', 'pick');   // メソッドをコールするには pick.* とします
echo $server->handle();

36.3.10.4. fault レスポンス用に使用する例外の指定

次の例は、Services_Exception の派生クラスに対して そのコードとメッセージを falut レスポンスで報告させるようにします。

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Services_Exceptions を fault レスポンスで報告させるようにします
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // メソッドをコールするには comb.* とします
$server->setClass('Services_Brush', 'brush'); // メソッドをコールするには brush.* とします
$server->setClass('Services_Pick', 'pick');   // メソッドをコールするには pick.* とします
echo $server->handle();

36.3.10.5. 独自のリクエストオブジェクトの利用

次の例は、独自のリクエストオブジェクトを作成し、 それをサーバに渡して処理します。

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Services/Request.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Services_Exceptions を fault レスポンスで報告させるようにします
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // メソッドをコールするには comb.* とします
$server->setClass('Services_Brush', 'brush'); // メソッドをコールするには brush.* とします
$server->setClass('Services_Pick', 'pick');   // メソッドをコールするには pick.* とします

// リクエストオブジェクトを作成します
$request = new Services_Request();

echo $server->handle($request);

36.3.10.6. 独自のレスポンスオブジェクトの利用

次の例は、独自のレスポンスクラスを作成し、 それをレスポンスとして返します。

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Services/Request.php';
require_once 'Services/Response.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// Services_Exceptions を fault レスポンスで報告させるようにします
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();
$server->setClass('Services_Comb', 'comb');   // メソッドをコールするには comb.* とします
$server->setClass('Services_Brush', 'brush'); // メソッドをコールするには brush.* とします
$server->setClass('Services_Pick', 'pick');   // メソッドをコールするには pick.* とします

// リクエストオブジェクトを作成します
$request = new Services_Request();

// 独自のレスポンスを使用します
$server->setResponseClass('Services_Response');

echo $server->handle($request);

36.3.10.7. リクエスト間でのサーバ定義のキャッシュ

次の例は、リクエスト間でサーバ定義をキャッシュします。

<?php
require_once 'Zend/XmlRpc/Server.php';
require_once 'Zend/XmlRpc/Server/Fault.php';
require_once 'Zend/XmlRpc/Server/Cache.php';
require_once 'Services/Request.php';
require_once 'Services/Response.php';
require_once 'Services/Exception.php';
require_once 'Services/Comb.php';
require_once 'Services/Brush.php';
require_once 'Services/Pick.php';

// キャッシュファイルを指定します
$cacheFile = dirname(__FILE__) . '/xmlrpc.cache';

// Services_Exceptions を fault レスポンスで報告させるようにします
Zend_XmlRpc_Server_Fault::attachFaultException('Services_Exception');

$server = new Zend_XmlRpc_Server();

// サーバ定義をキャッシュから取得しようとします
if (!Zend_XmlRpc_Server_Cache::get($cacheFile, $server)) {
    $server->setClass('Services_Comb', 'comb');   // メソッドをコールするには comb.* とします
    $server->setClass('Services_Brush', 'brush'); // メソッドをコールするには brush.* とします
    $server->setClass('Services_Pick', 'pick');   // メソッドをコールするには pick.* とします

    // キャッシュに保存します
    Zend_XmlRpc_Server_Cache::save($cacheFile, $server));
}

// リクエストオブジェクトを作成します
$request = new Services_Request();

// 独自のレスポンスを使用します
$server->setResponseClass('Services_Response');

echo $server->handle($request);