網路服務 (Web Service) =========== [網路服務](http://en.wikipedia.org/wiki/Web_service) 是一個軟體系統,設計來支援主機之間跨網絡相互存取。在 Web 應用程式,它通常用一套 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/en/ref.soap.php) 。請確定您嘗試本節中的例子前啟動此擴充。 定義服務提供者 (Service Provider) ------------------------- 正如我們上文所述,服務提供者是一個類別定義能被遠程調用的方法。Yii 依靠[doc comment](http://java.sun.com/j2se/javadoc/writingdoccomments/) and [class reflection](http://www.php.net/manual/en/language.oop5.reflection.php) 識別 哪些方法可以被遠程調用和他們的參數還有返回值。 讓我們以一個簡單的股票報價服務開始。這項服務允許客戶端請求指定股票的報價。我們確定服務提供者如下。請注意,我們定義一個 `StockController` 是藉由擴充 [CController] 的類別而來的,但這不是必需的。 ~~~ [php] class StockController extends CController { /** * @param string 股票 * @return float 股價 * @soap */ public function getPrice($symbol) { $prices=array('IBM'=>100, 'GOOGLE'=>350); return isset($prices[$symbol])?$prices[$symbol]:0; //...回傳 $symbol 的股價 } } ~~~ 在上面,我們透過在檔案註釋中的 `@soap` 標籤宣告 `getPrice` 方法為一個網路服務 API。依靠檔案註釋指定輸入的參數資料類型和返回值。其他的API可使用類似方式宣告。 定義網路服務動作 ---------------------------- 已經定義了服務提供者,我們使他能夠透過客戶端存取。特別的是,我們要建立一個控制器動作提供這個服務。要做到這一點很容易,在控制器類中定義一個 [CWebServiceAction] 動作。對於我們的例子中,我們把它放在 `StockController` 中。 ~~~ [php] class StockController extends CController { public function actions() { return array( 'quote'=>array( 'class'=>'CWebServiceAction', ), ); } /** * @param string 股票 * @return float 股價 * @soap */ public function getPrice($symbol) { //...return 回傳 $symbol 的股價 } } ~~~ 這就是我們需要建立的網路服務!如果我們嘗試存取 動作網址 `http://hostname/path/to/index.php?r=stock/quote`,我們將 看到很多 XML 內容,這實際上是我們定義的網路服務的 WSDL 描述。 > Tip|提示: 在預設情況下, [CWebServiceAction] 假設當前的控制器 是服務提供者。這就是因為我們在 `StockController` 中定義了 `getPrice` 方法。 使用網路服務 --------------------- 要完成這個例子,讓我們建立一個客戶端來使用我們剛剛建立的網路服務。例子中的客戶端用 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`. 如果類型不屬於上述任何原始類型,它被看作是複合型屬性。複合型類型被看做類別,他的屬性當做類別的空開成員變數,在檔案註釋中被用 `@soap` 標記。 我們還可以在原始或 複合型類型的後面透過附加 `[]` 來使用陣列類型。這將定義指定類型的陣列。 下面就是一個例子定義 `getPosts` 網頁 API,返回一個 `Post` 物件的陣列。 ~~~ [php] class PostController extends CController { /** * @return Post[] 一個 posts 清單 * @soap */ public function getPosts() { return Post::model()->findAll(); } } class Post extends CActiveRecord { /** * @var integer post 識別符號 * @soap */ public $id; /** * @var string post 標題 * @soap */ public $title; } ~~~ 類別映射 ------------- 為了從客戶端得到複合型參數,應用程式需要定義從 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 如果遠程方法基於某種原因不允許被調用(例如:未經授權的存取) 。