ウェブサービス
===========

[ウェブサービス](http://ja.wikipedia.org/wiki/Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9) とはネットワーク越しに、マシン同士の相互運用性をサポートする仕組みのことです。
ウェブアプリケーションの文脈においては、多くの場合、インターネットを介してアクセスできる API 群で、リクエストされたサービスをホストするリモートシステム上で実行されるものを、ウェブサービスと呼んでいます。
たとえば、[Flex](http://www.adobe.com/products/flex/) ベースのクライアントは、サーバで動いている PHP のウェブアプリケーションに実装された関数を呼び出すことが出来ます。
ウェブサービスはコミュニケーションプロトコルスタックの基礎部分を [SOAP](http://en.wikipedia.org/wiki/SOAP) に依存しています。

Yii は [CWebService] と [CWebServiceAction] を提供することで、ウェブアプリケーションでウェブサービスを提供する作業を簡単にします。
API は **サービスプロバイダ** と呼ばれるクラス群にまとめられます。
Yii はそれぞれのクラスについて、[WSDL](http://www.w3.org/TR/wsdl) スペックファイルを生成し、どの API が利用可能で、どのように呼び出すことができるのかを記述します。
クライアントによって API が呼び出されると、Yii が対応するサービスプロバイダをインスタンス化し、要求された API を実行してリクエストに応えます。


> Note|注意: [CWebService] は [PHP SOAP extension](http://www.php.net/manual/ja/ref.soap.php) に依存します。
この章の例を実行する前に拡張が有効になっていることを確認して下さい。

サービスプロバイダの定義
-------------------------

上で述べたように、サービスプロバイダとはリモートから呼び出し可能なメソッドを定義するクラスのことです。
Yii は [doc comment](http://java.sun.com/j2se/javadoc/writingdoccomments/) と [class reflection](http://php.net/manual/ja/book.reflection.php) に基づいて、どのメソッドがリモートから呼び出し可能であり、どのような引数をとり、そしてどのような返り値を返すのかを決定します。

まず単純な株価情報サービスからはじめましょう。
このサービスではクライアントが株価情報を要求できます。
サービスプロバイダを以下のように定義します。
プロバイダのクラス `StockController` を [CController] のサブクラスとして定義していることに注意してください。
ただし、このことは必須ではありません。

~~~
[php]
class StockController extends CController
{
	/**
	 * @param string the symbol of the stock
	 * @return float the stock price
	 * @soap
	 */
	public function getPrice($symbol)
	{
		$prices=array('IBM'=>100, 'GOOGLE'=>350);
		return isset($prices[$symbol])?$prices[$symbol]:0;
		 $symbol の株価を返す
	}
}
~~~

上記の例では、コメント (doc comment) に `@soap` タグをつけることによって、`getPrice` というメソッドがウェブサービス API であることを宣言しています。
引数と返値のデータタイプの定義も、コメント (doc comment) に依存します。
API を追加したい場合は、同じ方法で宣言して下さい。

ウェブサービスアクションを定義する
----------------------------

サービスプロバイダを定義したので、クライアントから呼び出し可能にする必要があります。
具体的には、サービスを公開するためのコントローラアクションを作成したいと思います。
これはコントローラクラスで、[CWebServiceAction] アクションを宣言することで容易に実現可能です。
サンプルコードでは `StockController` にそれを追加します。

~~~
[php]
class StockController extends CController
{
	public function actions()
	{
		return array(
			'quote'=>array(
				'class'=>'CWebServiceAction',
			),
		);
	}

	/**
	 * @param string the symbol of the stock
	 * @return float the stock price
	 * @soap
	 */
	public function getPrice($symbol)
	{
		//...$symbol の株価を返す
	}
}
~~~

これがウェブサービスを作るのに必要なことすべてです！
URL `http://hostname/path/to/index.php?r=stock/quote` にアクセスすれば、今定義したウェブサービスの WSDL を表す大量の XML が表示されます。

> ヒント: デフォルトでは、[CWebServiceAction] は現在のコントローラをサービスプロバイダとみなします。
これが `getPrice` メソッドを `StockController` クラスに定義した理由です。

ウェブサービスを利用する
---------------------

この例を完成させるために、できたばかりのウェブサービスを利用するクライアントを作ってみましょう。
例示したクライアントは PHP で書かれていますが、`Java`, `C#`, `Flex` など、その他の言語で書くこともできます。

~~~
[php]
$client=new SoapClient('http://hostname/path/to/index.php?r=stock/quote');
echo $client->getPrice('GOOGLE');
~~~

このスクリプトをブラウザか、コンソールで実行すると、`GOOGLE` の株価として `350` が表示される筈です。

データ型
----------

リモートから呼び出し可能なクラスメソッドとプロパティを定義する際に、入出力パラメータのデータ型を決める必要があります。
以下の基本データ型が利用可能です。

   - str/string: `xsd:string` に対応します;
   - int/integer: `xsd:int` に対応します;
   - float/double: `xsd:float` に対応します;
   - bool/boolean: `xsd:boolean` に対応します;
   - date: `xsd:date` に対応します;
   - time: `xsd:time` に対応します;
   - datetime: `xsd:dateTime` に対応します;
   - array: `xsd:string` に対応します;
   - object: `xsd:struct` に対応します;
   - mixed: `xsd:anyType` に対応します.

上記の基本型に当てはまらない場合は、複数のプロパティからなる複合型とみなされます。
複合型はクラスの形式で表され、コメント (doc comment) で `@soap` のマークが付けられたパブリックなメンバ変数が、プロパティになります。

また、基本型や複合型の末尾に `[]` をつけることで配列型を使うこともできます。
`[]` が指定された型の配列を定義します。

以下は `Post` オブジェクトの配列を返す `getPosts` ウェブ API の例です。

~~~
[php]
class PostController extends CController
{
	/**
	 * @return Post[] a list of posts
	 * @soap
	 */
	public function getPosts()
	{
		return Post::model()->findAll();
	}
}

class Post extends CActiveRecord
{
	/**
	 * @var integer post ID
	 * @soap
	 */
	public $id;
	/**
	 * @var string post title
	 * @soap
	 */
	public $title;

	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}
}
~~~

クラスの対応付け
-------------

複合型のデータをクライアントから受け取るためには、WSDL 型から PHP クラスへの対応を宣言する必要があります。
これは [CWebServiceAction] の [classMap|CWebServiceAction::classMap] プロパティを設定することで実現されます。

~~~
[php]
class PostController extends CController
{
	public function actions()
	{
		return array(
			'service'=>array(
				'class'=>'CWebServiceAction',
				'classMap'=>array(
					'Post'=>'Post',  // あるいは単に'Post'
				),
			),
		);
	}
	......
}
~~~

リモート呼び出しを阻止する
-------------------------------------

サービスプロバイダで [IWebServiceProvider] インタフェースを実装することで、リモートからのメソッド呼び出しを阻止することができます。
[IWebServiceProvider::beforeWebMethod] 内でプロバイダは [CWebService] のインスタンスをうけとり、[CWebService::methodName] によって、リクエストされたメソッド名を得ることができます。
何らかの理由 (例: 権限のないアクセスなど) でリモートからのメソッド呼び出しを許可したくない場合は、false を返すことで呼び出しを阻止できます。
