Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

PHP5でSOAPを用いたブックマークサービスを作成する

SOAPでWebサービスを提供する方法

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/10/27 11:00

PHP5の新機能としてSOAPが標準でサポートされました。本稿では、多くのWebサービスがSOAPに対応している中、クライアントとしてSOAPサービスを使うのではなく、サーバとしてSOAPサービスを提供してみるサンプルプログラムを紹介します。

はじめに

 GoogleAmazonを始め、多くの代表的なWebサービスでは、それ自身の機能を多くの利用者(開発者)に使ってもらうために、APIを提供しています。

 その仕組みとしてSOAPXML-RPCが使われていますが、今回はPHP5の新機能であるSOAP拡張機能を用いて、SOAPによるブックマークサービスを作成してみます。

対象読者

 PHP5を用いて開発している方を対象とします。

 また、今回用いるSOAPの拡張機能はPHP5から導入されたものなので、PHP4で開発している方はPEAR::SOAPを利用することで同様の事ができると思います。

必要な環境

 筆者の環境ではいわゆるLAMP構成で開発を行っています。対象OSは、Unix/Linuxです(Windowsでは、サンプルプログラムが動作しません)。以下に、必要なPHP Extension(PHP拡張)をリストアップします。

  • soap.so
  • mysqli.so
  • ……DBを操作する時に必要となりますが、MySQL以外を用いる場合は違っても構いません。
  • readline.so
  • ……サンプルプログラムのクライアント側で必要となりますが、必須ではありません。

 拡張機能を有効にするには「php.ini」に記述されている

''';'''extension=soap.so

 となっている部分を

extension=soap.so

 と、「; (セミコロン)」を消すだけで有効になるハズです。

データベースについて

 以下のようなテーブルが定義されています。

soap_bookmarksのテーブル
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>の間にサービスを定義していきます。

soap_bookmarks.wsdl
<?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による基本的なデータ型もありますが、配列などの集合体を表す場合もここに定義します。

soap_bookmarks.wsdl
<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>

メッセージを定義する部分

 サーバとやり取りするためのメッセージを定義します。

soap_bookmarks.wsdl
<!-- 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>
 他にも定義されていますが、割愛します。

受け付ける動作を定義する部分

 どのような動作を受け付けるかを定義します。プログラムでいうところのメソッドのようなものです。

soap_bookmarks.wsdl
<portType name="SoapBookmarksPortType">
  <!-- getPageという受付の定義です。 -->
  <operation name="getPage">
    <!-- 上の<message>で定義したgetPageメッセージを
入力することを定義しています。 -->
    <input message="typens:getPage" />
    <!-- 入力後<message>で定義したgetPageResponseが
返されることを定義しています。 -->
    <output message="typens:getPageResponse" />
  </operation>
</portType>
 他にも定義されていますが、割愛します。

メッセージフォーマットと伝送プロトコルを定義する部分

 SOAPでメッセージ交換を行う際のフォーマットと、伝送するプロトコルを定義します。

soap_bookmarks.wsdl
<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でサービスが置かれている場所を指定できます。

soap_bookmarks.wsdl
<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

 といったサービスに対応する処理を記述します。

サービスクラスを作成する

 クラスを作成し、サービスを提供するプログラムを記述します。大雑把ですが、サンプルのクラスは以下のようになっています。

bookmarks_server.class.php
<?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){
        :
    }

    // 省略します。
    :
    :
}
 詳しくは、サンプルをダウンロードして「bookmarks_server.class.php」をご覧ください。

 WSDLの<operation>で定義した

  • getPage
  • getPageList
  • addPage

 に対応する、同名のメソッドを作りました。

 また、プログラム内にPHP4では見掛けない書き方があるので、一部紹介します。

__construct()とは

$server = new bookmarks_server()

 上記のようにnew演算子を用いて、オブジェクトが生成された際に呼び出されるメソッド(コンストラクタ)です。主に初期化などを記述しておきます。

__destruct()とは

 コンストラクタとは逆に、オブジェクトが消滅する際に必ず呼び出されるメソッド(デストラクタ)です。データベースのコネクションやファイルクローズなどを明示的に、ここに記述しておけば、コネクションやファイルの閉じ忘れが防げると思います。

constとは

 クラス内で用いる定数を定義しておきます。呼び出す場合は、self::ConstNameとすることで呼び出せます。

 また、constpublic 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_resultfetch_assoc()で取り出した値は、連想配列のキーにカラム名が配置されているので、key()関数を用いてキー名を取り出し、クラスプロパティで設定した$intval配列内にキー名が存在するかどうかをin_array()関数を用いて検索しています。

 存在した場合はintval($value[$key])などで、配列の値をint型やstring型に変換しています。

サービスファイルの作成

 クラスファイルの作成が終わったら、SOAPサービスを作成します。PHP5のSoapServerというクラスを用いて、とても簡単に作成できます。

server.php
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」を見てみましょう。

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をキャッチするために記述しています。

bookmarks_client.php
<?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);
    }
}
 詳しくは、サンプルをダウンロードして「bookmarks_client.class.php」をご覧ください。

 コンストラクタで

__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を通して多くの利用者に使ってもらえるように、このサンプルを活用してください。

参考

  • LINEで送る
  • このエントリーをはてなブックマークに追加

修正履歴

  • 2006/04/23 08:32 2006/04/23 コメントより。サンプルファイルを修正しました。

著者プロフィール

  • ハタ(ハタ)

    PHPの魅力に取り付かれた一人。 現在はSeasar.PHPとしてSeasar(Java)をPHP5に移植する活動をしている。 http://blog.xole.net/(ブログ)

All contents copyright © 2006-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5