はじめに
GoogleやAmazonを始め、多くの代表的なWebサービスでは、それ自身の機能を多くの利用者(開発者)に使ってもらうために、APIを提供しています。
その仕組みとしてSOAPやXML-RPCが使われていますが、今回はPHP5の新機能であるSOAP拡張機能を用いて、SOAPによるブックマークサービスを作成してみます。
対象読者
PHP5を用いて開発している方を対象とします。
また、今回用いるSOAPの拡張機能はPHP5から導入されたものなので、PHP4で開発している方はPEAR::SOAPを利用することで同様の事ができると思います。
必要な環境
筆者の環境ではいわゆるLAMP構成で開発を行っています。対象OSは、Unix/Linuxです(Windowsでは、サンプルプログラムが動作しません)。以下に、必要なPHP Extension(PHP拡張)をリストアップします。
- soap.so
- mysqli.so
- readline.so
拡張機能を有効にするには「php.ini」に記述されている
''';'''extension=soap.so
となっている部分を
extension=soap.so
と、「; (セミコロン)」を消すだけで有効になるハズです。
データベースについて
以下のようなテーブルが定義されています。
CREATE TABLE soap_bookmarks ( ID INTEGER NOT NULL auto_increment, TITLE VARCHAR(255) NOT NULL, URL VARCHAR(255) NOT NULL, MEMO TEXT, PRIMARY KEY(ID,TITLE) );
MySQLだけで利用できるauto_increment
を除けば、他のデータベースでも利用できると思います。今回はこのテーブルを用います。
SOAPとWSDLについて
「SOAP」は、XMLを用いてメッセージ交換をするためのプロトコル仕様です。また、サーバ側がそのような仕組みをどのように提供するかを記述しているのが「WSDL」です。
soap_bookmarks.wsdlを読む
今回作成するSOAPによるブックマークサービスは、「soap_bookmarks.wsdl」にサービスを提供する仕組みが記述されています。どのような内容が記述されているか、順を追って説明します。
全体を定義する部分
WSDLでは、タグ<definitions>
と</definitions>
の間にサービスを定義していきます。
<?xml version ="1.0" encoding ="utf-8"?> <definitions name="SoapBookmarks" targetNamespace= "http://php.xole.net/soap_bookmarks/soap_bookmarks.wsdl" xmlns:typens= "http://php.xole.net/soap_bookmarks/soap_bookmarks.wsdl" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/">
データ型を定義する部分
サービスを提供する中で利用されるデータ型を定義します。xsdによる基本的なデータ型もありますが、配列などの集合体を表す場合もここに定義します。
<types> <xsd:schema targetNamespace="urn:SoapBookmarks"> <!-- SoapBookmarksElementという名の集合体を定義しています。 また、この集合体は以下の要素から成り立ちます。 --> <xsd:complexType name="SoapBookmarksElement"> <xsd:all> <!-- id要素はint型であることを定義しています。 --> <xsd:element name="id" type="xsd:int" /> <!-- title要素はstring型ということを定義しています。 --> <xsd:element name="title" type="xsd:string" /> <xsd:element name="url" type="xsd:string" /> <xsd:element name="memo" type="xsd:string" /> </xsd:all> </xsd:complexType> <!-- 配列を扱う集合体を定義します --> <xsd:complexType name="SoapBookmarksElementArray"> <xsd:complexContent> <!-- 集合体の配列を定義します --> <xsd:restriction base="soapenc:Array"> <!-- SoapBookmarksElementの配列であると定義します。 --> <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="typens:SoapBookmarksElement[]" /> </xsd:restriction> </xsd:complexContent> </xsd:complexType> </xsd:schema> </types>
メッセージを定義する部分
サーバとやり取りするためのメッセージを定義します。
<!-- getPageというメッセージは、idという名で int型でやり取りできることを定義しています。 --> <message name="getPage"> <part name="id" type="xsd:int" /> </message> <!-- getPageResponseというメッセージは、 ResultSetという名でSoapBookmarksElement型で やり取りできることを定義しています。 --> <message name="getPageResponse"> <part name="ResultSet" type="typens:SoapBookmarksElement" /> </message> <!-- getPageListResponseというメッセージは、 ResultSetsという名でSoapBookmarksElementArray型で やり取りできることを定義しています --> <message name="getPageListResponse"> <part name="ResultSets" type="typens:SoapBookmarksElementArray" /> </message> <!-- 他にも定義を複数記述することもできます。 --> <message name="addPage"> <part name="title" type="xsd:string" /> <part name="url" type="xsd:string" /> <part name="memo" type="xsd:string" /> </message> <!-- 真偽を表すboolean型も用いることができます。 --> <message name="addPageResponse"> <part name="bool" type="xsd:boolean" /> </message>
受け付ける動作を定義する部分
どのような動作を受け付けるかを定義します。プログラムでいうところのメソッドのようなものです。
<portType name="SoapBookmarksPortType"> <!-- getPageという受付の定義です。 --> <operation name="getPage"> <!-- 上の<message>で定義したgetPageメッセージを 入力することを定義しています。 --> <input message="typens:getPage" /> <!-- 入力後<message>で定義したgetPageResponseが 返されることを定義しています。 --> <output message="typens:getPageResponse" /> </operation> </portType>
メッセージフォーマットと伝送プロトコルを定義する部分
SOAPでメッセージ交換を行う際のフォーマットと、伝送するプロトコルを定義します。
<binding name="SoapBookmarksBinding" type="typens:SoapBookmarksPortType"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <operation name="getPage"> <soap:operation soapAction="typens:SoapBookmarksGetAction" /> <input> <soap:body use="encoded" namespace="typens:SoapBookmarks" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> </input> <output> <soap:body use="encoded" namespace="typens:SoapBookmarks" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" /> </output> </operation> </binding>
サービスを定義する部分
どこに、どんなサービスが提供されているかを定義します。localtion
でサービスが置かれている場所を指定できます。
<service name="SoapBookmarksService"> <port name="SoapBookmarksPort" binding="typens:SoapBookmarksBinding"> <soap:address location="http://php.xole.net/soap_bookmarks/server.php" /> </port> </service>
プログラムを作成する
WSDLを記述した事で、大部分の仕組みが定義されました。残るは、サービスを提供する側のプログラム作成です。サーバ側は、主に「soap_bookmarks.wsdl」で定義されている
getPage
getPageList
addPage
といったサービスに対応する処理を記述します。
サービスクラスを作成する
クラスを作成し、サービスを提供するプログラムを記述します。大雑把ですが、サンプルのクラスは以下のようになっています。
<?php class bookmarks_server { const MySQL_HOST = "localhost"; const MySQL_USER = "username"; const MySQL_PASSWD = "password"; const MySQL_DB = "testdb"; private static $strval = array("TITLE","URL","MEMO"); private static $intval = array("ID", "COUNT"); private static $db = null; public function __construct(){ self::$db = new mysqli(self::MySQL_HOST,self::MySQL_USER, self::MySQL_PASSWD, self::MySQL_DB); } public function __destruct(){ self::$db->close(); } public function getPage($id){ : } public function getPageList(){ : } public function addPage($title, $url, $memo = null){ : } // 省略します。 : : }
WSDLの<operation>
で定義した
getPage
getPageList
addPage
に対応する、同名のメソッドを作りました。
また、プログラム内にPHP4では見掛けない書き方があるので、一部紹介します。
__construct()とは
$server = new bookmarks_server()
上記のようにnew
演算子を用いて、オブジェクトが生成された際に呼び出されるメソッド(コンストラクタ)です。主に初期化などを記述しておきます。
__destruct()とは
コンストラクタとは逆に、オブジェクトが消滅する際に必ず呼び出されるメソッド(デストラクタ)です。データベースのコネクションやファイルクローズなどを明示的に、ここに記述しておけば、コネクションやファイルの閉じ忘れが防げると思います。
constとは
クラス内で用いる定数を定義しておきます。呼び出す場合は、self::ConstName
とすることで呼び出せます。
また、const
はpublic static $ConstName
とほぼ同等ですが、先頭に$
が付かないので、定数を用いる場合はこちらを使う方が楽です。
データ型に従って値を返す
getPage
のメソッドを見てみましょう。
public function getPage($id){ $id = $this->escape(intval($id)); $sql = "SELECT ID, TITLE, URL, MEMO FROM soap_bookmarks WHERE ID = ${id}"; $row = self::$db->query($sql)->fetch_assoc(); foreach($row as $key => $value){ $result[$key] = $this->convertResponse($value,$key); } return $result; }
データベースから取り出した連想配列の値を$this->convertResponse()
メソッドで変換しています。convertResponse()
メソッドでは以下のように値を変換しています。
private function convertResponse($value, $key = null){ $key = key($value); if( in_array($key, self::$intval) ){ return intval($value[$key]); } else if( in_array($key, self::$strval) ){ return strval($value[$key]); } else { return $value[$key]; } }
mysql_result
のfetch_assoc()
で取り出した値は、連想配列のキーにカラム名が配置されているので、key()
関数を用いてキー名を取り出し、クラスプロパティで設定した$intval
配列内にキー名が存在するかどうかをin_array()
関数を用いて検索しています。
存在した場合はintval($value[$key])
などで、配列の値をint
型やstring
型に変換しています。
サービスファイルの作成
クラスファイルの作成が終わったら、SOAPサービスを作成します。PHP5のSoapServer
というクラスを用いて、とても簡単に作成できます。
require "bookmarks_server.class.php"; $options = array( "encoding" => "UTF-8", "soap_version" => SOAP_1_2, ); $server = new SoapServer("soap_bookmarks.wsdl", $options); $server->setClass("bookmarks_server"); $server->handle();
これだけで「server.php」は完成です。これについて、もう少し掘り下げて説明します。
$server = new SoapServer("soap_bookmarks.wsdl", $options);
SoapServer
のコンストラクタの第一引数には、WSDLをセットします。"http://php.xole.net/soap_bookmarks/soap_bookmarks.wsdl"
などのようにネットワーク上のWSDLファイルをセットすることも可能です。
$server->setClass("bookmarks_server");
先ほど作成したbookmarks_server
クラスをセットします。これにより、WSDLで定義された<operation>
部にbookmarks_server
のメソッドがマッピングされます。
サービスが呼び出された時に、bookmarks_server
のメソッドが呼び出されます。後はこの「server.php」と「bookmarks_server.class.php」をWebサーバなどに配置すれば、サーバプログラムは完成です。
クライアントプログラムの作成
クライアントプログラムは、readline()
関数を用いて作成します。クライアントプログラムは、クラス化されたスクリプトと、それを呼び出すスクリプトの2つに分かれています。
まずは、「client.php」を見てみましょう。
<?php require "bookmarks_client.class.php"; try{ $client = new bookmarks_client( new SoapClient("soap_bookmarks.wsdl")); $client->operation(); } catch (Exception $e){ var_dump( $e->getMessage() ); } ?>
bookmarks_client
クラスのコンストラクタにSoapClient
をインスタンス化して渡しています。その後、インスタンス化されたbookmarks_client
オブジェクトのoperation()
メソッドを呼び出しています。
SoapClient
のコンストラクタで指定するWSDLファイルは、SoapServer
の時と同じように"http://php.xole.net/soap_bookmarks/soap_bookmarks.wsdl"
とすることも可能です。
try ~ catch(Exception $e)
となっているのは、「bookmarks_client.php」で発生したExceptionをキャッチするために記述しています。
<?php class bookmarks_client { private static $soap = null; public function __construct(SoapClient $soap){ self::$soap = $soap; } public function operation(){ $operation = ""; $list = array("getOne", "getAll", "add", "update", "delete"); foreach( $list as $key => $value ){ $operation .= $key . "-" . $value . PHP_EOL; } $select = readline($operation . "choose Operation [0 - " . (count($list) - 1) . "]:"); $op = $list[$select]; $this->$op(); } public function __call($name, $params){ throw new Exception("Undefined method: '" . $name . "'"); } private function getOne(){ $id = readline("Get Page ID? [integer] :"); $rows = self::$soap->getPage($id); if( count($rows) > 0 ){ echo $this->table($rows); } else { echo "ID(${id}) page does not exists" . PHP_EOL; } } private function getAll(){ $rows = self::$soap->getPageList(); foreach( $rows as $value ){ echo $this->table($value); } } private function add(){ $title = readline("Add Page Title? [string] :"); $url = readline("Page URL? [string] :"); $memo = readline("Page Memo(option) [string] :"); $res = self::$soap->addPage($title, $url, $memo); echo $this->table($res); } }
コンストラクタで
__construct(SoapClient $soap)
と記述し、SoapClient
オブジェクトを静的な変数self::$soap
に格納しています。
また、特徴的なのは、operation()
メソッドに記述されている
$op = $list[$select]; $this->$op();
の部分です。$変数名()
とすることで、その変数に格納されている文字列のメソッドを呼び出すことができます。
その後、選択されたメソッド内でreadline()
による入力を実行し、
self::$soap->addPage($title,$url,$memo);
と、SoapClient
によって生成されたWSDLで定義されている<operation>
部のメソッドを呼び出して実行しています。
サンプル動作
以下が、完成したクライアントプログラムを実行してみた例です(コンソール上から入力しています)。
/home/workspace/php/soap> php client.php 0-getOne 1-getAll 2-add 3-update 4-delete choose Operation [0 - 4]:2 Add Page Title? [string] :CodeZine Page URL? [string] :http://codezine.jp/ Page Memo(option) [string] :開発者のための実装系Webマガジン ---------- Response: true ----------
/home/workspace/php/soap> php client.php 0-getOne 1-getAll 2-add 3-update 4-delete choose Operation [0 - 4]:1 ---------- ID: 1 TITLE: CodeZine URL: http://codezine.jp/ MEMO: 開発者のための実装系Webマガジン ---------- ---------- ID: 2 TITLE: blog.xole.net URL: http://blog.xole.net/ MEMO: 自分のWeblog ----------
まとめ
今回のサンプルではSOAPの簡単な例しか紹介できませんでしたが、PHP5のSOAP拡張を用いれば簡単に導入することができます。「SOAPはちょっと敷居が高い」や「XMLは難しい」などと言われていますが、まずはやってみることが大切です。あなたの面白いサービスがSOAPを通して多くの利用者に使ってもらえるように、このサンプルを活用してください。
参考
- PHPマニュアル 『CXVII. SOAP関数』
- PHPマニュアル 『第19章 クラスとオブジェクト (PHP 5)』
- PHPマニュアル 『第20章 例外(exceptions)』
- PHPマニュアル 『LXXXI. 改良版MySQL拡張サポート(mysqli)』
- PHPマニュアル 『CVII. GNU Readline』
- PEAR 『Package Information: SOAP』
- IBM 『サービス指向アーキテクチャー(SOA)のポリシーをサポート』 Boris Lublinsky 著、2004年11月