ログイン記事を投稿する会員登録はこちら!ヘルプ

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

[中級] SOAPでWebサービスを提供する方法
ハタ [著]  | 
評価:4.27 σ = 1.19
公開:05/10/27
Web
DB
EZ新着
PHP5の新機能としてSOAPが標準でサポートされました。本稿では、多くのWebサービスがSOAPに対応している中、クライアントとしてSOAPサービスを使うのではなく、サーバとしてSOAPサービスを提供してみるサンプルプログラムを紹介します。
サンプルファイル 4.8KB (276)

はじめに

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

参考

関連記事
web
web
web
lang

変更履歴

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


著者紹介
ハタ (ハタ)
PHPの魅力に取り付かれた一人。
現在はSeasar.PHPとしてSeasar(Java)をPHP5に移植する活動をしている。
http://blog.xole.net/(ブログ)
コメント
(最新日付順:新着コメントRSS配信中!

■Webサービス
コメント評:+0
 
Bad!
 
Good!

トランザクションが複数あって、1つのトランザクションというWebサービスを構築した場合(ロング・ラニング・トランザクション)、DBへのコネクションのインスタンスは、1つだけ生成してトランザクション内で再利用できるのでしょうか?

■windows で動かすには
コメント評:+0
 
Bad!
 
Good!

投稿者のハタです。
Windowsで動かすには、bookmarks_client.phpのoperation部でreadline()関数を使っている部分を修正する必要があります。
これはコンソールからの文字入力を可能にするreadlineモジュールの関数を使っています。この部分がwindowsでは使えません。

コマンドラインについては以下を参考にしてください。
http://php.net/manual/ja/features.commandline.php
■windows で動かすには
コメント評:+0
 
Bad!
 
Good!

このサンプルを windows で動かすにはどう修正をしたらよいでしょうか?
■サンプルファイルの更新
コメント評:+0
 
Bad!
 
Good!

編集部の斉木です。
新しいサンプルファイルをアップいたしました。ご指摘と修正ありがとうございました。
■サンプルファイルを修正しました。
コメント評:+0
 
Bad!
 
Good!

投稿者のハタです。
ご指摘の通りサンプルファイルの記述が間違っていました。
サンプルファイルを修正しましたので、後ほどダウンロードが出来る状態になると思います。

ご指摘ありがとうございました。
■サンプルソース
コメント評:+0
 
Bad!
 
Good!

bookmarks_server.class.php
updatePageメソッドで
SQL組み立て部分
MEMO = '${$memo}'

MEMO = '${memo}'
かと。

名前:*
メールアドレス(名前にリンク):
URL(名前にリンク):
タイトル:
内容(テキストのみ1200文字まで、リンクタグ入力不可):*
アイコン:
なし

利用規約に同意して

トラックバック
この記事のトラックバックURL:
スパム対策で、トラックバックはデフォルトで非公開とし、編集部チェックを通した上で公開させて頂いております。重複した登録などにご注意ください。
プログラマー向けポータルサイトであるCodeZineに「PHP5でSOAPを用いたブックマークサービスを作成する」が掲載されました。これはPHP 5とSOAP拡張モジュールを用いてamazonにアクセスする実装 続きを読む
PHP5+SOAP [p0t より]
CodeZine:PHP5でSOAPを用いたブックマークサービスを作成する  G... 続きを読む
Trackback (2)
記事は編集作業を経て公開されていますが、あくまで情報提供を第一の目的としたものであり、 内容には、不正確な記述、執筆者の予断や誤解に基づくもの、リンク切れ、環境要件が古いものが含まれていることがあります。 記事(翻訳記事を除く)の訂正に関しては、編集部の判断により随時対応することがありますが、各著作権者および(株)翔泳社はその内容の完全性を一切保障しません。 「投稿」の性質上、各著作権者は読者より訂正の依頼があったとしても対応できないこともあります。 記事内容の運用により派生した損害を含むあらゆる結果について、各著作権者および(株)翔泳社は一切の責任を持ちません。 各著作権者は記事内容に関するあらゆるサポートに付いてもその義務を放棄しています。あくまで記事は投稿され、編集を経て、公開された時点で完結したものであり、公開以降もサポートするかどうかは各者の任意事項となります。あらかじめご了承ください。
最新ニュース ≫一覧
最新記事 ≫一覧
一般投稿
RFC3820(代理証明書)のC#による実装
Windows PowerShell 入門(3)−スクリプト編
VB.NET版O/Rマッピングツール「ObjectService」の使い方(継承設定)
Advanced/W-ZERO3 [es]でカメラアプリを作ろう
Windows PowerShell 入門(2)−基本操作編 2
最近のコメント
・蛛E・ ・・,ハE聰・Q褞・ ,趺P・・・蛛E・, 趺P・・・・D,・...(Kirikik:03/19)
babydaemons 様 ご指摘ありがとうございます! 確かに誤ってお...(sai:03/17)
typoしてます。 3ページの「拡張メソッドの定義(C#)」のソースの3行目の...(babydaemons:03/17)
編集部ブログ
New York Times Perl Profilerがなんかすごそう()
書籍検索でSQLインジェクション()
イケテルRails勉強会@九州大学に参加してみました()
サイトメンテナンスと新会員制度システムに関するお知らせ()
OpenIDへの対応に悩み中()
サイト統計
はてなブックマーク合計数
昨日までの登録メンバー数昨日までの総メンバー数
昨日の訪問者数昨日の訪問者数