使用 PHP 開發基於 Web 服務的應用程序

PHP SOAP 擴展php

SOAP 的全稱爲簡單對象訪問協議 (Simple Object Access Protocol)。它是一種基於 XML 的,可擴展的通訊協議。SOAP 提供了一種標準,使得運行在不一樣平臺上並使用不一樣的編程語言編寫的應用程序能夠互相進行通訊。SOAP 的可擴展性和平臺無關性使得它被普遍用做 Web 服務的通訊協議。html

因爲 Java 語言提供了對 SOAP 的良好支持,一般基於 Web 服務的應用程序使用 Java 語言編寫。對於廣大的 PHP 程序員來講,可能會有一點小小的不滿 – PHP 的較早版本根本沒有對 SOAP 的直接支持,只能經過 PEAR(the PHP Extension and Application Repository) 中的 SOAP 庫或者第三方產品 NuSOAP 來開發 Web 服務。不過最近的版本已經改變了這一情況。自 PHP 5 開始新增了內置的 SOAP 擴展 (ext/soap),今後咱們不須要下載額外的擴展庫或是代碼包來開發基於 SOAP 的應用程序了。接下來讓咱們來看看 SOAP 擴展中都有哪些內容。程序員

回頁首web

PHP 5 中的 SOAP 擴展 (ext/soap)數據庫

PHP 5 最先發布的版本 5.0.0 中就已經提供了 SOAP 擴展,不過當時的 PHP 手冊中聲明這個擴展是試驗性 (experimental) 的。實際上當時的版本已經實現了比較完善的功能,也沒有必要爲此而擔憂。目前這個擴展還在不斷地完善,早期版本中的大部分 bug 都已經獲得了修正,目前最新的版本 (5.3.0) 中已經提供了比較完整的對 SOAP 的支持,並且咱們有理由相信,之後的版本還會更好。編程

SOAP 擴展庫結構數組

ext/soap 中包括六個預約義的類,經過這些類,咱們能夠建立 Web 服務端 (SoapServer 類 ),客戶端 (SoapClient 類 ),處理 SOAP 請求和應答 (SoapHeader, SoapParam, SoapVar 類 ),診斷錯誤 (SoapFault 類 )。這些類之間的聯繫如圖 1 所示:瀏覽器


圖 1. SOAP 擴展的結構
圖 1. SOAP 擴展的結構 

SOAP 服務類 SoapServer緩存

SoapServer 類用來開發 Web 服務端應用程序。這個類中包含建立,設置和操縱 Web 服務的函數。有兩種方式能夠向 Web 服務中添加操做 (Operation)。一種方式是直接添加已定義的函數,另外一種方式是添加已定義好的類,從而將該類的公有成員函數添加到 Web 服務中。服務器

另外一個須要說明的特性是,PHP 支持兩種 Web 服務的模式:WSDL 模式和 non-WSDL 模式,爲了便於理解,咱們首先從 Web 服務的兩種實現模式開始提及。

PHP 中 Web 服務的兩種模式:WSDL 模式和 non-WSDL 模式

對於 Web 服務來講,主要有兩種實現模式 – 契約先行 (Contract First) 模式和代碼先行 (Code Fist) 模式。

契約先行模式的實現中,首要工做是定義針對這個 Web 服務的藉口的 WSDL(Web Services Description Language,Web 服務描述語言 ) 文件。WSDL 文件中描述了 Web 服務的位置,可提供的操做集,以及其餘一些屬性。WSDL 文件也就是 Web 服務的「契約」。「契約」訂立以後,再據此進行服務器端和客戶端的應用程序開發。這種模式對應上節所說的 WSDL 模式。咱們後文中介紹的例子就是使用這一模式實現的。

與契約先行模式不一樣,代碼先行模式中,第一步工做是實現 Web 服務端,而後根據服務端的實現,用某種方法(自動生成或手工編寫)生成 WSDL 文件。可是因爲 PHP 自己並無提供從 Web 服務實現代碼中生成 WSDL 文件的方法,所以就要以 non-WSDL 模式鏈接服務端,即不經過 WSDL 文件建立 SoapServer 和 SoapClient 示例,而是直接向構造函數傳遞必要的參數。固然,代碼先行模式也有其餘的解決方法,一些集成的 PHP 開發工具(如 Zend Studio)就提供了根據 Web 服務實現代碼生成 WSDL 文件的功能。

SOAP 客戶端類 SoapClient

SOAP 客戶端類 SoapClient 用於開發 Web 服務的客戶端程序。可用的成員函數主要有建立客戶端實例,調用可用操做,查詢可用操做和數據類型等。除此以外還包括了可用於程序調試的函數 – 獲取上次請求和應答的 SOAP 數據。

SOAP 參數類 SoapHeader, SoapParam, SoapVar

SoapParam 和 SoapVar 主要用來封裝用於放入 SOAP 請求中的數據,他們主要在 non-WSDL 模式下使用。事實上,在 WSDL 模式下,SOAP 請求的參數能夠經過數組方式包裝,SOAP 擴展會根據 WSDL 文件將這個數組轉化成爲 SOAP 請求中的數據部分,因此並不須要這兩個類。而在 non-WSDL 模式下,因爲沒有提供 WSDL 文件,因此必須經過這兩個類進行包裝。

SoapHeader 類用來構造 SOAP 頭,SOAP 頭能夠對 SOAP 的能力進行必要的擴展。SOAP 頭的一個主要做用就是用於簡單的身份認證,後面會有例子說明這一點。

SOAP 異常類 SoapFault

這個類從 PHP 的 Exception 類繼承而來,能夠用來實現 SOAP 中的異常處理機制,由 SOAP 服務端拋出。SOAP 客戶端能夠接收該類的實例,用於獲取有用的調試信息。

回頁首

安裝 SOAP 擴展

爲了使用 SOAP 擴展,咱們就須要在 Web 服務器上安裝它。這裏有幾個因素須要考慮。

  • 安裝的前置條件:在官方的使用手冊中能夠找到,ext/soap 擴展使用了 GNOME XML 庫,所以在安裝 SOAP 擴展以前須要安裝這個庫(須要 2.5.4 以上版本)。
  • PHP 是否已安裝:
    • 若是你想在安裝 PHP 的同時加入 SOAP 擴展,那再簡單不過了。若是是下載 PHP 源代碼本身編譯安裝的狀況,則只須要在編譯時的 configure 命令中添加選項 --enable-soap 便可。若是是直接使用二進制文件安裝(一般只用於 Windows 平臺),安裝包中則已經包括了這一擴展,不須要額外安裝。
    • 而若是須要在已經安裝好的 PHP 上添加 SOAP 擴展,須要作的工做就要多一些。在編譯 SOAP 擴展的源代碼以前須要使用 phpize 命令設置編譯環境,而後再使用 configure 命令,以後編譯並安裝 SOAP 擴展。

編譯安裝 SOAP 擴展以後,咱們還須要修改 PHP 的配置文件,以便 SOAP 擴展能夠正確的被 PHP 加載。對於 Linux 平臺來講,須要在 php.ini 中加入以下代碼:

extension = php_soap.so

而對於 Windows 平臺,須要加入的代碼爲:

extension = php_soap.dll

除此以外,可能還須要設置擴展庫的位置,這一信息在 php.ini 的 extension_dir 域中保存,例如:

extension_dir = "/usr/local/php/lib/"

上面的工做完成以後,還須要注意的是 SOAP 擴展在配置文件中有獨立的代碼段:


清單 1.php.ini 中 SOAP 擴展的設置
soap]

; Enables or disables WSDL caching feature.

soap.wsdl_cache_enabled=1

; Sets the directory name where SOAP extension will put cache files.

soap.wsdl_cache_dir="C:\xampp\tmp"

; (time to live) Sets the number of second while cached file will be used

; instead of original one.

soap.wsdl_cache_ttl=86400

其中的三項設置主要是用來指定 PHP 處理 WSDL 文件時使用緩存的行爲。這三項設置分別說明是否啓用緩存、緩存文件的路徑、緩存的生存時間。啓用緩存會加快 PHP 處理 WSDL 文件的速度,但最好在調試代碼時關閉緩存,以免一些因緩存行爲而出現的問題。

回頁首

一個簡單的例子:產品資料查詢

設想這樣一個場景:A 公司是筆記本電腦的生產商,B 公司是 A 公司的經銷商。B 公司須要向他們的客戶提供一個產品信息查詢的網站,用戶輸入產品編號就能夠查詢到該產品的詳細信息,包括 CPU,內存,屏幕尺寸,硬盤等。因爲常常有新產品面世,因此 A 公司的產品信息數據庫會頻繁地更新,對此比較好的解決方案是 A 公司提供一個產品信息查詢的 Web 服務,而 B 公司開發客戶端來調用這個 Web 服務提供的操做。整個系統的架構以下圖所示:


圖 2. 產品資料查詢系統架構
圖 2. 產品資料查詢系統架構 

主要的組成部分有:

  • 產品信息數據庫,其中存儲了產品代碼,CPU 信息,內存容量,屏幕尺寸,硬盤容量等產品信息。
  • Web 服務端,它發佈一個 Web 服務,響應客戶端的查詢請求,並將查詢結果放入 SOAP 應答中返回給客戶端。
  • 客戶機,它接收瀏覽器發來的查詢條件,以今生成 SOAP 請求發送給 Web 服務端,並接收 SOAP 應答,將其發送到瀏覽器並顯示出來。瀏覽器的輸出如圖 3 所示。

本質上 Web 服務端和客戶機都是一個相對獨立的 Web 應用程序,它們之間只是經過 SOAP 消息進行通訊。在不改變通訊「契約」的狀況下,Web 服務端和客戶端內部實現的改變均不影響這個系統的功能。因此對於「契約」- 即 WSDL 文件的定義就是很是重要的一步。


圖 3. 產品信息查詢系統頁面
圖 3. 產品信息查詢系統頁面 

回頁首

WSDL 文件的編寫

前面提到過,PHP 自己並無提供能夠自動生成 WSDL 文件的方法,所以就須要咱們本身編寫 WSDL 文件。WSDL 的結構雖然比較清楚,但徹底依靠文本編輯器建立一個 WSDL 文件依然是個艱難的任務。這是由於 WSDL 中的元素比較多,每一個元素還有若干屬性,要徹底掌握這些比較困難。另外一方面,若是沒有開發環境的輔助,咱們在編寫 WSDL 文件中的錯誤就很難被發現,存在任何一個微小的錯誤(例如標籤名 message 誤寫成了 massage),咱們的應用程序也沒法正常工做。所以在編寫 WSDL 文件時使用適當的開發工具是很必要的。下面咱們介紹兩種藉助開發工具生成 WSDL 文件的方法,一種適用於契約先行模式,另外一種適用於代碼先行模式。

使用 PDT(PHP Development Tool) 編寫 WSDL 文件

PDT(PHP Development Tool) 是一個基於 Eclipse 的集成開發環境,它提供了對於 PHP 開發中各類需求的良好支持。咱們能夠在菜單中選擇 New->Other...,而後在彈出的窗口中選擇 Web Service 下的 WSDL File,而後輸入文件名,建立 WSDL 文件,PDT 會生成一個默認的 WSDL「框架」,並以圖形化的方法顯示出來,對應本文的例子,WSDL 文件的圖形化表示以下圖:


圖 4. PDT 中 WSDL 文件的圖形化表示
圖 4. PDT 中 WSDL 文件的圖形化表示 

咱們能夠看到,這個圖形化的表示方法包含了 WSDL 的所有要素:端口,消息,綁定 (Bindings),數據類型和服務。對於除了數據類型以外的部分,咱們只須要點擊相應部分做出修改便可。對於數據類型部分的修改,則須要點擊最右側的灰色箭頭,打開數據類型的視圖,對應本文需求的數據類型視圖以下:


圖 5. PDT 中輸入的數據類型的圖形化表示
圖 5. PDT 中輸入的數據類型的圖形化表示 

圖 6. PDT 中輸出的數據類型的圖形化表示
圖 6. PDT 中輸出的數據類型的圖形化表示 

以後咱們能夠修改數據類型的名稱,添加元素,編輯複雜數據類型,修改元素的類型和名稱等。全部上述的修改都會被 PDT 自動轉換成對應的 WSDL 語句。

以上三個視圖構成了 WSDL 的完整描述,點擊界面下方的 Source 標籤,就能夠看到 WSDL 文件的源代碼:


清單 2. WSDL 源代碼
<?xml version="1.0"encoding="UTF-8"standalone="no"?> 
 <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"  xmlns:tns="http://soapexample.cn/ProductQuery/"  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"xmlns:xsd="http://www.w3.org/2001/XMLSchema"  name="ProductQuery"targetNamespace="http://soapexample.cn/ProductQuery/"> 
 <wsdl:types> 
    <xsd:schema targetNamespace="http://soapexample.cn/ProductQuery/"> 
      <xsd:element name="ProductQueryCode"> 
        <xsd:complexType> 
          <xsd:sequence> 
            <xsd:element name="ProductCode"type="xsd:string"/> 
          </xsd:sequence> 
        </xsd:complexType> 
      </xsd:element> 
      <xsd:element name="ProductSpec"> 
        <xsd:complexType> 
          <xsd:sequence> 
            <xsd:element name="ProductCode"type="xsd:string"></xsd:element> 
            <xsd:element name="CPU"type="xsd:string"></xsd:element> 
            <xsd:element name="RAM"type="xsd:string"></xsd:element> 
            <xsd:element name="Screen"type="xsd:string"></xsd:element> 
            <xsd:element name="HDD"type="xsd:string"></xsd:element> 
          </xsd:sequence> 
        </xsd:complexType> 
      </xsd:element> 
    </xsd:schema> 
  </wsdl:types> 
  <wsdl:message name="QuerySpecRequest"> 
    <wsdl:part element="tns:ProductQueryCode"name="QueryCode"/> 
  </wsdl:message> 
  <wsdl:message name="QuerySpecResponse"> 
    <wsdl:part element="tns:ProductSpec"name="Specification"/> 
  </wsdl:message> 
  <wsdl:portType name="ProductQuery"> 
    <wsdl:operation name="QuerySpec"> 
      <wsdl:input message="tns:QuerySpecRequest"/> 
      <wsdl:output message="tns:QuerySpecResponse"/> 
    </wsdl:operation> 
  </wsdl:portType> 
  <wsdl:binding name="ProductQuerySOAP"type="tns:ProductQuery"> 
    <soap:binding style="document"transport="http://schemas.xmlsoap.org/soap/http"/> 
      <wsdl:operation name="QuerySpec"> 
        <soap:operation soapAction="http://soapexample.cn/ProductQuery//> 
          <wsdl:input> 
            <soap:body use="literal"/> 
          </wsdl:input> 
          <wsdl:output> 
            <soap:body use="literal"/> 
          </wsdl:output> 
        </wsdl:operation> 
      </wsdl:binding> 
 <wsdl:service name="LaptopProduct"> 
    <wsdl:port binding="tns:ProductQuerySOAP"name="ProductQuerySOAP"> 
      <soap:address location="http://soapexample.cn/ProductQueryService.php"/> 
    </wsdl:port> 
  </wsdl:service> 
 </wsdl:definitions>

至此,咱們就完成了對 WSDL 文件的編寫。這種方式適用於契約先行模式。根據已經獲得的 WSDL 文件,咱們就能夠繼續開發服務端和客戶端的 Web 應用程序。

使用 Zend Studio 生成 WSDL 文件

第二種方法適用於代碼先行模式。Zend Studio 是另外一個優秀的 PHP 集成開發環境,它相對於 PDT 的優點之一就是提供了自動生成 WSDL 文件的功能。固然,要使用自動生成功能須要知足一些條件。第一個條件就是必須使用類定義來包含須要發佈的 Web 服務中的操做(以 public 函數的方式定義);另外,須要以 PHP Doc 方式在註釋中聲明所提供操做 (Operation) 的參數和返回值。PHP Doc 與 Java Doc 的語法相似,如下是一個簡單的例子:


清單 3. PHP 類定義代碼
<?php 
 class SimpleClass 
 { 
 /** 
 * add two parameters, then return result 
 * 
 * @paraminteger $a1 
 * @paraminteger $a2 
 * @returninteger 
 */ 
 function add( $a1, $a2 ) 
 { 
 return $a1 + $a2; 
 } 
 } 
 ?>

生成對應的 WSDL 文件的操做十分簡單,在菜單中選取 File->Export...,接着在彈出的窗口中選擇 PHP 下的 WSDL File,接着指定要生成 WSDL 文件的類,以及輸出的文件名,點擊 Finish,Zend Studio 就會根據上面的類定義和對應的 PHP Doc 自動生成的 WSDL 文件。與上例對應的 WSDL 文件的視圖以下:


圖 7. Zend Studio 生成的 WSDL 文件示例
圖 7. Zend Studio 生成的 WSDL 文件示例 

能夠看到,惟一沒有自動生成的是 Web 服務的位置,只要將這一項填寫好,WSDL 的建立工做就完成了。

回頁首

開發 Web 服務端

接下來將介紹的是開發 Web 服務端程序的方法。前面已經提到過,PHP 實現 Web 服務的方式有兩種:WSDL 模式和 non-WSDL 模式。兩種模式下對於 Web 服務端和客戶端的實現都有一些不一樣,本節主要介紹 Web 服務端的實現方法。

SOAP 擴展中用於實現 Web 服務端的類是 SoapServer,每一個 SoapServer 類的實例對應一個 Web 服務。假設咱們已經使用以前介紹的方法使用 PDT 編寫了一個 WSDL 文件,文件名爲 QueryService.wsdl。這樣,咱們就應該使用 WSDL 模式來建立咱們的 Web 服務。應用 WSDL 模式建立 SoapServer 類實例的語句爲:


清單 4. 建立 SoapServer 類的實例(WSDL 模式)
$server = new SoapServer( "./QueryService.wsdl" );

因爲 WSDL 中已經包含足夠描述 Web 服務的信息,因此咱們只需向 SoapServer 的構造函數提供 WSDL 文件的路徑就能夠了。而對於 non-WSDL 模式來講,建立 SoapServer 的對象就須要咱們提供更多的信息,例如服務的位置,編碼方案,SOAP 協議的版本等:


清單 5. 建立 SoapServer 類的實例(non-WSDL 模式)
$server = new SoapServer( null, array( "uri" => "http://soapexample.cn/ProductQuery", 
"encoding" => "ISO-8859-1", 
"soap_version" => SOAP_1_2 ) );

下面咱們須要定義一個 Web 服務的操做 (Operation)。一般的方法是定義一個函數 (Function),函數的功能就是對應的 Web 服務中操做的功能。例以下面的函數,它實現了查詢產品信息的功能。


清單 6. 產品信息查詢函數
function QuerySpec( $param ) 
 { 
 try{ 
 $conn = getDBConnection(); 
 $result = queryFromDB( $conn, $param->ProductCode ); 
 }catch( Exception $e ){ 
 printf( "ErrorMessage: %s", $e->__toString() ); 
 } 
 return array( "ProductCode" => $result['PRODUCTCODE'], 
"CPU" => $result['CPU'], 
"RAM" => $result['RAM'], 
"Screen" => $result['Screen'], 
"HDD" => $result['HDD'] ) ; 
 }

這裏須要說明一些注意事項:

  • 函數的名稱必須是 WSDL 中已定義的一個操做名稱,即添加到 SoapServer 中的函數必須與 WSDL 中定義的操做相對應。
  • 輸入到函數中的參數是一個類的實例,類的結構與 WSDL 中定義的數據類型相對應。經過訪問參數中以元素名字爲變量名稱的成員變量 ($param->ProductCode),就能夠取得 SOAP 請求中的相應數據。對於僅以順序方式 ( 數據類型定義中只有以 <sequence> 標籤包含的簡單類型序列 ) 定義的數據類型,那麼這個類的實例中僅僅包含簡單類型的成員。對於有多於一個層次的數據結構,那麼類中還將包含描述下層數據結構的類的示例,以此類推,造成一個多層次的結構。在 SOAP 擴展中,不管是客戶端仍是服務端接收到的 SOAP 數據包,都會被解析成這種數據結構。
  • 函數的返回值則不須要包裝成類的結構,使用數組便可。對於 WSDL 模式來講,能夠直接使用關聯數組,關聯數組的鍵值必須與數據類型定義中的名稱相對應。對於更多層次的數據結構,須要在這個數組中加入其餘的關聯數組來實現層次化的表達。而若是想要採用 non-WSDL 模式,則須要把每一個元素使用 SoapParam 類包裝構造函數的兩個參數爲元素名稱和元素的值,而後放入數組中。

最後的工做是把已經定義好的函數加入 Web 服務中,成爲可調用的操做:


清單 7. 把定義好的函數加入 Web 服務中
$server->addFunction( "QuerySpec" ); 
 $server->handle();

最後一個語句調用 SoapServer::handle() 是必要的,做用是通知 SoapServer 開始處理 Web 服務的請求,若是缺乏了這一語句,Web 服務就不會被啓動。至此,咱們就完成了對 Web 服務端的開發。

回頁首

開發客戶端

客戶端的實現方法一樣分爲 WSDL 模式和 non-WSDL 模式兩種。首先咱們須要建立 SoapClient 對象,對於本文中的例子,WSDL 模式的代碼以下:


清單 8. 建立 SoapClient 類的實例(WSDL 模式)
$client = new SoapClient('./ProductQuery.wsdl');

與 soapServer 相同,只須要向構造函數提供 WSDL 文件的路徑便可;non-WSDL 的例子則是這樣:


清單 9. 建立 SoapClient 類的實例(non-WSDL 模式)
$client = new SoapClient( null, array( "location" => "http://soapexample.cn/ProductQuery", 
"uri" => "http://soapexample.cn/ProductQueryService.php", 
"style" => SOAP_DOCUMENT,"use" => SOAP_LITERAL, 
"soap_version" => SOAP_1_2, 
"encoding" => "ISO-8859-1" ) );

因爲沒有 WSDL 文件可供使用,咱們至少須要提供服務的存在位置,其餘的域是可選的,例如名字空間,編碼方案,SOAP 協議版本等。

接下來咱們就能夠調用已經發布的操做了。可是在這樣作以前,咱們還須要瞭解兩點:

  • 咱們能夠調用哪些操做,這些操做須要的參數是什麼?
  • 參數的數據類型定義是什麼?

要搞清楚這兩點,咱們固然能夠去直接閱讀 WSDL 文件,但因爲 WSDL 文件可能會很複雜,因此有的時候要弄清楚這些問題可能會花費很多的時間;另外,有些時候咱們尚未辦法獲得 WSDL 文件。固然還存在其餘方法,SoapClient 類中提供了兩個頗有用的成員函數可讓咱們輕鬆得到 Web 服務中提供的操做,以及相關的數據結構定義:


清單 10. 查看 Web 服務開放的方法和數據類型
print_r( $client->__getFunctions() ); 
 print_r( $client->__getTypes() );

經過這兩行代碼,咱們能夠看到瀏覽器顯示的結果:


清單 11. Web 服務開放的方法和數據類型示例
Array 
 ( 
  [0] => ProductSpec QuerySpec(ProductQueryCode $QueryCode) 
 ) 
 Array 
 ( 
  [0] => struct ProductQueryCode { 
    string ProductCode; 
  } 
  [1] => struct ProductSpec { 
    string ProductCode; 
    string CPU; 
    string RAM; 
    string Screen; 
    string HDD; 
  } 
 )

因而咱們能夠知道,咱們能夠調用 Web 服務中的 QuerySpec 操做,而且得知了這個操做的輸入和輸出數據的定義。這個時候咱們就能夠着手編寫調用 QuerySpec 的代碼了。下面兩個語句均可以完成調用的功能,它們的做用是等效的:


清單 12. 調用 Web 服務開放的操做
$result = $client->__soapCall('QuerySpec', array( array( "ProductCode" => '1175-PXA') ) ); 
 $result = $client->QuerySpec( array( array( "ProductCode" => '1175-PXA') ) );

能夠直接使用 Web 服務中的操做名稱做爲函數進行調用,就像真的在調用本地定義的函數同樣,這種方法比較直觀;也能夠把操做名稱做爲參數傳給 SoapClient::__soapCall(),效果是同樣的。

須要注意的依然是參數的結構。和服務端同樣,輸入的參數依然須要組織成數組的形式,可是有一點點不一樣,已定義好的數組又被放入了最外層的數組中。看起來最外面的一層包裝彷佛有些多餘,可是若是去掉,程序是不會獲得正確結果的。

最後咱們須要使用 Web 服務端返回的結果。與前面提到的相似,服務端返回的數據也是以對象嵌套的方式組織的,因此咱們只須要用成員引用操做符 (->) 便可得到相應域的值:


清單 13. 使用 SOAP 應答中的數據
echo "Product Code:" . $client->ProductCode . "<br />"; 
 echo "Product Code:" . $client->CPU . "<br />"; 
 echo "Product Code:" . $client->RAM . "<br />"; 
 echo "Product Code:" . $client->Screen . "<br />"; 
 echo "Product Code:" . $client->HDD . "<br />";

稍加修改,咱們就能夠獲得以前給出的在瀏覽器中的顯示效果了。

到這裏咱們的工做彷佛已經結束了。可是實際的開發過程是不可能如此順利的,若是咱們的代碼沒有獲得正確的結果怎麼辦?因此,咱們須要瞭解一些使用 PHP 開發 SOAP 應用程序時的用到的調試知識。

回頁首

調試咱們的程序 —— 捕獲異常

考慮一個咱們編寫代碼時極可能出現的錯誤:在爲調用的操做輸入參數時,參數中某個元素的名字錯誤或是沒有提供。例如咱們把查詢須要的產品代碼的名字錯誤地寫成了"ProductCod",這時運行客戶端代碼,是不可能獲得正確的結果的。咱們怎麼才能發現這個錯誤呢?

PHP 5 中新增了不少編程語言中都提供的異常處理機制 try...catch,咱們能夠把客戶端的實現代碼包含在這個結構裏 ( 須要注意的是,PHP 5 中不支持 finally 子句 ):


清單 14. 加入異常處理部分的客戶端代碼
try 
 { 
 $client = new SoapClient('./ProductQuery.wsdl'); 
 $result = $client->__soapCall('QuerySpec', array( array( "ProductCod" => '1175-PXA' ) ) ); 
 echo "Product Code:" . $client->ProductCode . "<br />"; 
 echo "Product Code:" . $client->CPU . "<br />"; 
 echo "Product Code:" . $client->RAM . "<br />"; 
 echo "Product Code:" . $client->Screen . "<br />"; 
 echo "Product Code:" . $client->HDD . "<br />"; 
 } 
 catch (SoapFault $e) 
 { 
 echo $e; 
 }

咱們會在瀏覽器中獲得這樣的輸出:


清單 15. Web 服務端返回的異常信息:缺乏屬性
SoapFault exception: 
 [Client] SOAP-ERROR: Encoding: object hasn't 'ProductCode' property in 
 C:\xampp\htdocs\soapTest\GetProductInfo.php:17 
 Stack trace: 
 #0 C:\xampp\htdocs\soapTest\GetProductInfo.php(17): SoapClient->__soapCall('QuerySpec', 
 Array) 
 #1 {main}

在這個例子中,異常是由 SoapClient 對象直接拋出的,它檢查輸入的參數,若是發現某個 WSDL 文件中定義的項沒有被提供,便拋出這個異常,告訴咱們"ProductCode"屬性沒有被提供。而咱們經過有針對性的檢查代碼,就能夠比較容易的發現錯誤所在。

服務端一樣也可能拋出異常,這些異常一般是客戶端檢查時沒法發現的,例如某些邏輯錯誤,若是咱們輸入了一個不合法的產品代碼,就可能捕獲到服務端拋出的「不合法的產品代碼」異常。爲了實現這一功能,咱們須要在服務端的代碼中加入下面的一段語句:


清單 16. Web 服務端拋出產品代碼無效的異常
if( !$result ){ 
 throw new SoapFault("Server", "Invalid Product Code!"); 
 }

這段語句在未獲得查詢結果的狀況下(這時認爲緣由是提供了無效的產品代碼),拋出了一個 SoapFault 異常,用於建立 SoapFault 對象的參數包括錯誤代碼,以及必要的錯誤信息。須要注意的是,錯誤代碼只能使用 SOAP 標準中已定義的值,使用其餘的值不會返回正確的信息。具體可以使用的值能夠查看 W3C 的 SOAP 文檔。這樣,在客戶端提供無效的產品代碼時,會捕獲到的異常信息:


清單 17. Web 服務端返回的異常信息:產品代碼無效
SoapFault exception: 
 [SOAP-ENV:Server] Invalid Product Code! in C:\xampp\htdocs\soapTest\GetProductInfo.php:17 
 Stack trace: 
 #0 C:\xampp\htdocs\soapTest\GetProductInfo.php(17): SoapClient->__soapCall('QuerySpec', Array) 
 #1 {main}

因而咱們就知道提供的產品代碼是無效的了。

回頁首

調試咱們的程序 —— 跟蹤 SOAP 數據

在咱們調試 SOAP 程序時,僅僅依賴異常處理機制是不夠的。咱們在調用 Web 服務提供的操做時,若是參數的結構錯誤,客戶端和服務端頗有可能都不拋出異常,例如前面在實現客戶端應用程序時提到的問題,咱們把參數最外面的數組去掉:


清單 18. 錯誤的調用方法
$result = $client->__soapCall('QuerySpec', array( "ProductCode" => '1175-PXA') );

這時咱們看不到任何輸出,說明根本沒有捕獲到異常,但很顯然程序沒有正常工做。咱們如何來發現錯誤所在呢?

SoapClient 類提供了兩個函數,用來跟蹤客戶端發出的 SOAP 請求和從服務端收到的 SOAP 應答。咱們能夠在 try...catch 結構的後面加入以下代碼:


清單 19. 跟蹤 SOAP 請求和應答
echo "Request :<br/>".htmlspecialchars($client->__getLastRequest())."<br/>"; 
 echo "Response :<br/>".htmlspecialchars($client->__getLastResponse())."<br/>";

另外爲了開啓跟蹤功能,咱們須要在 SoapClient 的構造函數中輸入額外的一個參數:


清單 20. 開啓 SOAP 跟蹤功能
$client = new SoapClient('./ProductQuery.wsdl' , array( 'trace' => 1 ) );

這樣,咱們就能夠在瀏覽器中觀察到 SOAP 請求和應答的內容:


清單 21. 錯誤的 SOAP 請求和應答
Request: 
 <?xml version="1.0" encoding="UTF-8"?> 
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
 xmlns:ns1="http://soapexample.cn/ProductQuery/"> 
 <SOAP-ENV:Body> 
 <ns1:ProductQueryCode/> 
 </SOAP-ENV:Body> 
 </SOAP-ENV:Envelope> 

 Response: 
 <?xml version="1.0" encoding="UTF-8"?> 
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:ns1="http://soapexample.cn/ProductQuery/"> 
 <SOAP-ENV:Body> 
 <ns1:ProductSpec> 
 <ProductCode/> 
 <CPU/> 
 <RAM/> 
 <Screen/> 
 <HDD/> 
 </ns1:ProductSpec> 
 </SOAP-ENV:Body> 
 </SOAP-ENV:Envelope>

能夠發現,SOAP 請求的結構跟咱們指望的不一樣,咱們就能夠知道,是輸入的參數不正確形成的,改正了這個錯誤以後,咱們能夠看到正確的 SOAP 請求和應答:


清單 22. 正確的 SOAP 請求和應答
Request: 
 <?xml version="1.0" encoding="UTF-8"?> 
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
 xmlns:ns1="http://www.ibm.com/ProductQuery/"> 
 <SOAP-ENV:Body> 
 <ns1:ProductQueryCode> 
 <ProductCode>1175-PXA</ProductCode> 
 </ns1:ProductQueryCode> 
 </SOAP-ENV:Body> 
 </SOAP-ENV:Envelope> 

 Response: 
 <?xml version="1.0" encoding="UTF-8"?> 
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
 xmlns:ns1="http://www.ibm.com/ProductQuery/"> 
 <SOAP-ENV:Body> 
 <ns1:ProductSpec> 
 <ProductCode>1175-PXA</ProductCode> 
 <CPU>Centrino T9400</CPU> 
 <RAM>3GB DDR3</RAM> 
 <Screen>14.1 inch.</Screen> 
 <HDD>300GB 5400rpm</HDD> 
 </ns1:ProductSpec> 
 </SOAP-ENV:Body> 
 </SOAP-ENV:Envelope>

回頁首

結束語

使用 PHP 開發基於 Web 服務的應用程序總的來講是比較簡單的。從前文的例子中能夠看到,咱們不須要不少的代碼就能夠建立一個簡單的 Web 服務端和客戶端,惟一的小麻煩多是建立 WSDL 文件,但咱們藉助一些 PHP 集成開發環境的幫助同樣能夠輕鬆解決。這可讓習慣使用 PHP 開發 Web 應用程序的程序員不須要學習其餘語言就可以開發本身的基於 Web 服務的應用程序。

本文中的例子相對來講比較簡單,但咱們必須瞭解,PHP 的 SOAP 擴展目前也存在着一些不足之處。例如:

  • PHP 對於某些 SOAP 協議中的元素不能正確解析,例如目前 SoapServer 類並不能處理客戶端發來的 SOAP 請求中的 Header 部分,這使得一些基於 Header 的特性沒法在 PHP 中獲得實現,例如權限驗證等。
  • 因爲 PHP 是弱類型語言,而 SOAP 協議中對類型的定義是比較嚴格的,因此 PHP 沒法僅僅根據代碼生成可供使用的 WSDL 文件,只能經過 PHP Doc 之類的機制在註釋中聲明,從而使輔助工具得到參數的類型。
  • PHP 的弱類型性質還形成 SOAP 擴展對類型的檢查並不嚴格,若是服務端的實現中若是返回了類型錯誤的數據(例如應該返回類型爲 integer 的數據,實際上卻返回了字符串),則並不會產生異常,而只是將返回的數據解釋成 WSDL 中定義的類型,可是這種轉換一般是不能獲得正確結果的。
  • PHP 的文檔中對於 SOAP 調用的參數構造介紹不多,關聯數組構造方法與 WSDL 中的數據定義的映射關係也不是十分清晰易懂。對於數據類型較爲複雜的狀況,單純使用數組構造一個具備不少層次的參數結構也是困難且容易出錯的。

幸運的是,PHP 的開發和維護者們始終把 SOAP 擴展看作 PHP 中重要的組成部分,自從 PHP 5.0.0 中開始提供 SOAP 擴展以來,它就沒有中止過更新,每一次新的版本都會有新特性發布,同時也會修正不少原有的缺陷。最新的版本 (5.3.0) 最近剛剛發佈,其中對於上述的問題 1 和 4 都有很好的解決。因此咱們有理由相信,PHP 會提供對 SOAP 愈來愈完善的支持。


參考資料

相關文章
相關標籤/搜索