Web 服務最近很是流行,其中基於 REST 的服務吸引了大部分的關注。REST 之因此流行,是因爲它簡單、直接和可以處理現有 HTTP 方法。可是也要記住,REST 並非惟一的方法:SOAP,即 Simple Object Access Protocol,是一種更正式和更標準的處理 Web 信息交換問題的方法。 mysql
雖然基於 SOAP 的服務實現通常被認爲是一個複雜的、耗費時間的過程,可是有許多工具能夠顯著簡化這個過程。其中一個工具是 Zend Framework,它是使用 PHP 構建可擴展 Web 應用的一個完整的 MVC 框架。除了許多強大功能以外 — OOP 形式、i18n 支持、查詢和頁面緩存和 Dojo 集成等等 — Zend Framework 也提供了大量經過它的 Zend_Soap 組件建立和部署 SOAP 服務的工具包。 sql
在本文中,您將瞭解使用 Zend Framework 建立一個簡單的基於 SOAP Web 服務的過程。除了學習處理客戶端請求和返回符合 SOAP 響應以外,您還將瞭解處理異常和產生 SOAP 錯誤的過程。最後,您也將使用 Zend_Soap 自動生成一個描述 SOAP 服務的 WSDL 文件,從而使客戶端能 「自動發現」 SOAP 服務 API。 shell
回頁首 數據庫
首先,咱們要理解一些關於 SOAP 的詞彙。SOAP 是使用與語言無關的 XML 在 Web 上交換信息的方法,從而容許與使用不一樣語言編寫的應用實現互連。這個 XML 經過 HTTP 傳輸協議在客戶端和服務器之間進行傳輸,它具備強數據類型,能夠保證數據完整性。 編程
REST 以資源 和行爲 爲中心,而 SOAP 則與之不一樣,它基於方法 和 數據類型。一個 REST 服務通常只有 4 個操做,它們對應於 4 個 HTTP 方法 GET、POST、PUT 和 DELETE,而 SOAP 服務則沒有這樣的限制;開發人員能夠根據本身的須要使用更多或較少的方法。並且,這些方法通常是經過 POST HTTP 方法調用的,而這個方法與所請求的操做類型則徹底沒有關係。 數組
爲了演示 SOAP 的用法,咱們使用一個簡單的例子。假設您有一個社交書籤應用,而您但願容許第三方開發人員使用 SOAP 嚮應用添加書籤和從應用查詢書籤。通常狀況下,您會使用getBookmark()和addBookmark()等函數實現一組服務對象,並將這些服務對象經過一個 SOAP 服務器發佈出去。這個服務也會負責將 SOAP 數據類型轉換成原生數據類型,解析 SOAP 請求數據包,執行相應的服務器函數,並生成包含結果的一個 SOAP 響應數據包。 瀏覽器
清單 1 顯示了getBookmark()過程的一個可能的 SOAP 請求例子: 緩存
POST /soap HTTP/1.1 Host: localhost Connection: Keep-Alive User-Agent: PHP-SOAP/5.3.1 Content-Type: application/soap+xml; charset=utf-8 Content-Length: 471 <?xml version="1.0" encoding="UTF-8"?> <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://example.localhost/index/soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"> <env:Body> <ns1:getBookmark env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"> <param0 xsi:type="xsd:int">4682</param0> </ns1:getBookmark> </env:Body> </env:Envelope> |
而 清單 2 顯示了一個示例響應:
HTTP/1.1 200 OK Date: Wed, 17 Mar 2010 17:13:28 GMT Server: Apache/2.2.14 (Win32) PHP/5.3.1 X-Powered-By: PHP/5.3.1 Content-Length: 800 Keep-Alive: timeout=5, max=100 Connection: Keep-Alive Content-Type: application/soap+xml; charset=utf-8 <?xml version="1.0" encoding="UTF-8"?> <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://example.localhost/index/soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:enc="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc"> <ns1:getBookmarkResponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"> <rpc:result>return</rpc:result> <return enc:itemType="xsd:string" enc:arraySize="3" xsi:type="enc:Array"> <item xsi:type="xsd:string">http://www.google.com</item> <item xsi:type="xsd:string">http://www.php-programming-solutions.com </item> <item xsi:type="xsd:string">http://www.mysql-tcr.com</item> </return> </ns1:getBookmarkResponse> </env:Body> </env:Envelope> |
在一個典型的 SOAP 事務中,服務器會接收一個像 清單 1 所示的以 XML 編碼的請求,解析這個 XML,執行相應的服務對象方法,而後返回一個如 清單 2 所示的以 XML 編碼的響應到請求客戶端。客戶端一般可以解析和響應這個 SOAP,並將它轉換成一個特定語言的對象或數據結構以做進一步處理。您能夠選擇使用一個 WSDL 文件告訴客戶端關於可用函數的信息,以及輸入參數和返回值的個數和數據類型。
Zend Framework 具備 SOAP 客戶端和服務器的實現,同時支持自動生成 WSDL 文件。服務器和客戶端實如今 PHP 中封裝了 SOAP 擴展;這意味着若是 PHP 沒有包含 SOAP 擴展支持,那麼它們將沒法生效。這樣,使用帶有原生擴展的 Zend Framework 庫大大簡化了開發過程,由於開發人員只須要定義一組實現服務 API 的對象,並將它們附加到服務器以便處理到達的請求。下面的各部分將詳細討論這個方面。
在開始實現一個 SOAP 服務以前,您須要知道一些注意點和約定。在本文中,我將假定您擁有正常運行的 Apache、PHP+SOAP 和 MySQL 的開發環境,Zend Framework 會安裝到您的 PHP 包含路徑,同時您要熟悉 SQL、XML 和 SOAP 基礎知識。我還將假定您熟悉使用 Zend Framework 進行應用開發的基本原則,理解行爲與控制器之間的交互,並熟悉 Zend_Db 數據庫抽象層。最後,我還假定您的 Apache Web 服務器配置支持虛擬主機和使用 .htaccess 進行 URL 重寫。若是您不熟悉這些概念,您能夠經過本文的 參考資料 的連接瞭解更多信息。
在本文中您將實現的示例 SOAP 服務容許第三方開發人員將產品添加到應用數據庫,以及編輯、刪除和查詢應用數據庫中的產品列表。它使用下面的函數,您可使用一個標準的 SOAP 客戶端訪問全部這些函數:
首先,咱們要建立一個標準的 Zend Framework 應用,它包含本文所顯示的代碼上下文。使用 Zend Framework 工具腳本(Windows® 上則是 zf.bat,UNIX 是 zf.sh)建立一個新項目,以下所示:
shell> zf.bat create project example |
您如今能夠在您的 Apache 配置中爲這個應用定義一個新的虛擬主機,如 http://example.localhost/,而後將虛擬主機的文檔根目錄指向應用的 public/ 目錄。而後,若是您訪問這個主機,您應該能看到默認的 Zend Framework 歡迎頁面,如 圖 1 所示。
下一步是初始化應用數據庫。因此,咱們要建立一個新的 MySQL 表來保存產品信息,以下所示:
mysql> CREATE TABLE IF NOT EXISTS products ( -> id int(11) NOT NULL AUTO_INCREMENT, -> title varchar(200) NOT NULL, -> shortdesc text NOT NULL, -> price float NOT NULL, -> quantity int(11) NOT NULL, -> PRIMARY KEY (id) -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
在這個表中填入一些示例記錄以便開始開發,以下所示:
mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(1, -> 'Ride Along Fire Engine', 'This red fire engine is ideal for toddlers who -> want to travel independently. Comes with flashing lights and beeping horn.', -> 69.99, 11); Query OK, 1 row affected (0.08 sec) mysql> INSERT INTO products (id, title, shortdesc, price, quantity) VALUES(2, -> 'Wind-Up Crocodile Bath Toy', 'This wind-up toy is the perfect companion -> for hours of bathtub fun.', 7.99, 67); Query OK, 1 row affected (0.08 sec) |
最後一步是爲 Zend Framework 自動加載配置名稱空間。這個步驟將在須要時實現自動加載特定應用類到應用中。在這裏,我假設應用的名稱空間爲Example,而特定應用類(如 SOAP 服務類)將存儲在 $PROJECT/library/Example/ 中。因此,要修改應用配置文件 $PROJECT/application/configs/application.ini 並添加下面一行到文件中:
autoloaderNamespaces[] = "Example_" |
您如今已經完成了建立一個 SOAP 服務的全部準備工做!
由於這是一個示例應用,我將盡可能保持簡單,並只在默認模塊的 IndexController 上建立一個處理 SOAP 請求的動做;然而,在實際中,您可能但願使用一個單獨的控制器處理 SOAP 請求。編輯文件 $PROJECT/application/controllers/IndexController.php,而後添加新的動做,如 清單 3 所示:
<?php class IndexController extends Zend_Controller_Action { public function soapAction() { // disable layouts and renderers $this->getHelper('viewRenderer')->setNoRender(true); // initialize server and set URI $server = new Zend_Soap_Server(null, array('uri' => 'http://example.localhost/index/soap')); // set SOAP service class $server->setClass('Example_Manager'); // handle request $server->handle(); } } |
清單 3 傳遞一個 null 值到對象構造函數的第一個參數,以非 WSDL 模式初始化了一個新的 Zend_Soap_Server 對象。若是以非 WSDL 模式建立服務器,咱們必須指定服務器 URI;在 清單 3 中,這是在做爲第二個參數傳遞給構造函數的選項數組中指定的。
接下來,服務器對象的setClass()函數用於將一個服務類附加到服務器上。這個類實現了 SOAP 服務的可用函數;這個服務器將在 SOAP 請求的響應中自動調用這些函數。若是您喜歡,您也可使用addFunction()和loadFunctions()函數將用戶自定義函數附加到服務器上,而不須要使用setClass()函數附加整個類。
正如以前所提到的,Zend_Soap_Server 類並無提供它本身的 SOAP 服務器實現;它只是封裝了 PHP 的內置 SOAP 擴展。所以,一旦全部先決條件都準備好後,清單 3 中的handle()函數會負責初始化內置的 PHP SoapServer 對象,將它傳遞給請求對象,並調用該對象的handle()函數處理 SOAP 請求。
雖然全部這些都作好了,可是這還遠遠不夠,由於服務類尚未定義。接下來咱們使用 清單 4 中的代碼建立這個類定義,將建立的類定義保存到 $PROJECT/library/Example/Manager.php:
<?php class Example_Manager { /** * Returns list of all products in database * * @return array */ public function getProducts() { $db = Zend_Registry::get('Zend_Db'); $sql = "SELECT * FROM products"; return $db->fetchAll($sql); } /** * Returns specified product in database * * @param integer $id * @return array|Exception */ public function getProduct($id) { if (!Zend_Validate::is($id, 'Int')) { throw new Example_Exception('Invalid input'); } $db = Zend_Registry::get('Zend_Db'); $sql = "SELECT * FROM products WHERE id = '$id'"; $result = $db->fetchAll($sql); if (count($result) != 1) { throw new Exception('Invalid product ID: ' . $id); } return $result; } } ?> |
清單 4 建立了一個單獨的服務類,它包含兩個函數。getProducts()函數使用 Zend_Db 查詢表中全部的產品記錄,而後將它們做爲一個數組返回,而getProduct()函數則接收一個產品標識符,而後只返回特定的一條記錄。而後 SOAP 服務器將這個方法的返回值轉換成一個 SOAP 響應數據包,並將它返回給發送請求的客戶端。清單 8 包含一個響應數據包的例子:
若是您還在疑惑 Zend_Db 是在哪裏初始化的,我能夠告訴您它是在應用啓動加載器中初始化的,即 $PROJECT/application/Bootstrap.php。這個 Bootstrap.php 包含了一個_initDatabase()函數,它建立 Zend_Db 適配器,並將它註冊到應用註冊表中。清單 5 顯示這部分代碼:
<?php class Bootstrap extends Zend_Application_Bootstrap_Bootstrap { protected function _initDatabase() { $db = new Zend_Db_Adapter_Pdo_Mysql(array( 'host' => 'localhost', 'username' => 'user', 'password' => 'pass', 'dbname' => 'example' )); Zend_Registry::set('Zend_Db', $db); } } |
爲了看到實際結果,要建立一個 SOAP 客戶端(清單 6),而後使用它鏈接 SOAP 服務,並請求getProducts()函數。
<?php // load Zend libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Soap_Client'); // initialize SOAP client $options = array( 'location' => 'http://example.localhost/index/soap', 'uri' => 'http://example.localhost/index/soap' ); try { $client = new Zend_Soap_Client(null, $options); $result = $client->getProducts(); print_r($result); } catch (SoapFault $s) { die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring); } catch (Exception $e) { die('ERROR: ' . $e->getMessage()); } ?> |
SOAP 客戶端將會產生一個請求數據包(清單 7)。
<?xml version="1.0" encoding="UTF-8"?> <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://example.localhost/index/soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"> <env:Body> <ns1:getProducts env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"/> </env:Body> </env:Envelope> |
這個服務器產生一個使用 SOAP 編碼的響應(清單 8)。
<?xml version="1.0" encoding="UTF-8"?> <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://example.localhost/index/soap" xmlns:ns2="http://xml.apache.org/xml-soap" xmlns:enc="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc"> <ns1:getProductsResponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"> <rpc:result>return</rpc:result> <return enc:itemType="ns2:Map" enc:arraySize="2" xsi:type="enc:Array"> <item xsi:type="ns2:Map"> <item> <key xsi:type="xsd:string">id</key> <value xsi:type="xsd:string">1</value> </item> <item> <key xsi:type="xsd:string">title</key> <value xsi:type="xsd:string">Ride Along Fire Engine</value> </item> <item> <key xsi:type="xsd:string">shortdesc</key> <value xsi:type="xsd:string">This red fire engine is ideal for toddlers who want to travel independently. Comes with flashing lights and beeping horn.</value> </item> <item> <key xsi:type="xsd:string">price</key> <value xsi:type="xsd:string">69.99</value> </item> <item> <key xsi:type="xsd:string">quantity</key> <value xsi:type="xsd:string">11</value> </item> </item> ... </return> </ns1:getProductsResponse> </env:Body> </env:Envelope> |
而後 SOAP 客戶端會將這個響應轉換成一個原生的 PHP 數組,它能夠被進一步處理或檢查,如 圖 2 所示。
在瞭解瞭如何經過 SOAP 查詢數據後,如今咱們瞭解一下如何添加和刪除數據。
在 Example_Manager 類中實現一個addProduct()函數很是簡單。清單 9 演示了實現方法:
<?php class Example_Manager { /** * Adds new product to database * * @param array $data array of data values with keys -> table fields * @return integer id of inserted product */ public function addProduct($data) { $db = Zend_Registry::get('Zend_Db'); $db->insert('products', $data); return $db->lastInsertId(); } } |
清單 9 中的addProduct()函數接收一個新產品記錄做爲鍵-值對數組,而後使用 Zend_Db 對象的insert()函數將記錄寫入到數據庫表中。它最後返回最新插入記錄的 ID。
刪除一個產品也同樣簡單:只須要增長一個deleteProduct()函數,它接收產品 ID 做爲輸入,而後使用 Zend_Db 的delete()函數從數據庫刪除這個記錄。清單 10 展現了這個方法的實現:
<?php class Example_Manager { /** * Deletes product from database * * @param integer $id * @return integer number of products deleted */ public function deleteProduct($id) { $db = Zend_Registry::get('Zend_Db'); $count = $db->delete('products', 'id=' . $db->quote($id)); return $count; } } |
在 清單 10中,傳遞給delete()函數的第二個參數指定了執行 DELETE 操做時使用的約束或過濾器。使用這個參數很重要;由於若是不做限制,Zend_Db 將刪除表中的全部記錄。
最後,清單 11 展現了一個updateProduct()函數,它可用於更新一個產品記錄的值。這個函數接收兩個輸入參數 — 產品 ID 和一個包含修改記錄的數組 — 並使用 Zend_Db 的update()函數對數據庫表執行一個 UPDATE 查詢。
<?php class Example_Manager { /** * Updates product in database * * @param integer $id * @param array $data * @return integer number of products updated */ public function updateProduct($id, $data) { $db = Zend_Registry::get('Zend_Db'); $count = $db->update('products', $data, 'id=' . $db->quote($id)); return $count; } } |
您能夠在如 清單 12 所示的一個 SOAP 客戶端嘗試全部這些函數:
<?php // load Zend libraries require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Soap_Client'); // initialize SOAP client $options = array( 'location' => 'http://example.localhost/index/soap', 'uri' => 'http://example.localhost/index/soap' ); try { // add a new product // get and display product ID $p = array( 'title' => 'Spinning Top', 'shortdesc' => 'Hours of fun await with this colorful spinning top. Includes flashing colored lights.', 'price' => '3.99', 'quantity' => 57 ); $client = new Zend_Soap_Client(null, $options); $id = $client->addProduct($p); echo 'Added product with ID: ' . $result; // update existing product $p = array( 'title' => 'Box-With-Me Croc', 'shortdesc' => 'Have fun boxing with this inflatable crocodile, made of tough, washable rubber.', 'price' => '12.99', 'quantity' => 25 ); $client->updateProduct($id, $p); echo 'Updated product with ID: ' . $id; // delete existing product $client->deleteProduct($id); echo 'Deleted product with ID: ' . $id; } catch (SoapFault $s) { die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring); } catch (Exception $e) { die('ERROR: ' . $e->getMessage()); } ?> |
上面所列的全部函數的一個共同問題是:它們沒有做任何的輸入驗證。在實際中,忽略這種驗證會對您的應用數據庫的完整性形成嚴重影響,而且可能很快會致使數據損壞(最好狀況)或徹底破壞(最壞狀況)。
幸虧,Zend Framework 包含了一個 Zend_Validate 組件,它爲最多見狀況提供了內置的驗證器。您能夠將這個特性與 Zend_Soap_Server 的registerFaultException()函數結合,用於測試客戶端所提供的請求數據,而後爲不一樣的錯誤狀況返回一個 SOAP 錯誤信息。
要了解它是如何工做的,咱們要先經過擴展 Zend_Exception 建立一個自定義異常類,如 清單 13 所示:
<?php class Example_Exception extends Zend_Exception {} |
將這個類保存到 $PROJECT/library/Example/Exception.php。
接下來,修改各個服務類函數,使它們包含輸入驗證,並在輸入數據無效或缺乏數據時拋出自定義異常。清單 14 展現了修改的 Example_Manager 類:
<?php class Example_Manager { // define filters and validators for input private $_filters = array( 'title' => array('HtmlEntities', 'StripTags', 'StringTrim'), 'shortdesc' => array('HtmlEntities', 'StripTags', 'StringTrim'), 'price' => array('HtmlEntities', 'StripTags', 'StringTrim'), 'quantity' => array('HtmlEntities', 'StripTags', 'StringTrim') ); private $_validators = array( 'title' => array(), 'shortdesc' => array(), 'price' => array('Float'), 'quantity' => array('Int') ); /** * Returns list of all products in database * * @return array */ public function getProducts() { $db = Zend_Registry::get('Zend_Db'); $sql = "SELECT * FROM products"; return $db->fetchAll($sql); } /** * Returns specified product in database * * @param integer $id * @return array|Example_Exception */ public function getProduct($id) { if (!Zend_Validate::is($id, 'Int')) { throw new Example_Exception('Invalid input'); } $db = Zend_Registry::get('Zend_Db'); $sql = "SELECT * FROM products WHERE id = '$id'"; $result = $db->fetchAll($sql); if (count($result) != 1) { throw new Example_Exception('Invalid product ID: ' . $id); } return $result; } /** * Adds new product to database * * @param array $data array of data values with keys -> table fields * @return integer id of inserted product */ public function addProduct($data) { $input = new Zend_Filter_Input($this->_filters, $this->_validators, $data); if (!$input->isValid()) { throw new Example_Exception('Invalid input'); } $values = $input->getEscaped(); $db = Zend_Registry::get('Zend_Db'); $db->insert('products', $values); return $db->lastInsertId(); } /** * Deletes product from database * * @param integer $id * @return integer number of products deleted */ public function deleteProduct($id) { if (!Zend_Validate::is($id, 'Int')) { throw new Example_Exception('Invalid input'); } $db = Zend_Registry::get('Zend_Db'); $count = $db->delete('products', 'id=' . $db->quote($id)); return $count; } /** * Updates product in database * * @param integer $id * @param array $data * @return integer number of products updated */ public function updateProduct($id, $data) { $input = new Zend_Filter_Input($this->_filters, $this->_validators, $data); if (!Zend_Validate::is($id, 'Int') || !$input->isValid()) { throw new Example_Exception('Invalid input'); } $values = $input->getEscaped(); $db = Zend_Registry::get('Zend_Db'); $count = $db->update('products', $values, 'id=' . $db->quote($id)); return $count; } } |
在 清單 14 中,服務 API 加強後包含了對全部輸入參數的驗證。對於大多數 API 函數,Zend_Validate::is()靜態函數提供了一種測試輸入參數的便捷方法;在某些狀況下,一個額外的 Zend_Filter_Input 過濾器鏈會用於驗證和過濾輸入。在輸入驗證過程當中發生的任何錯誤都會產生一個 Example_Exception 類的實例。
最後一步是告訴 SOAP 服務器自動將所產生的 Example_Exception 實例轉換成 SOAP 錯誤。通常經過使用registerFaultException()函數將異常類註冊到 SOAP 服務器上,如 清單 15 所示的修改後的IndexController::soapAction:
<?php class IndexController extends Zend_Controller_Action { public function soapAction() { // disable layouts and renderers $this->getHelper('viewRenderer')->setNoRender(true); // initialize server and set URI $server = new Zend_Soap_Server(null, array('uri' => 'http://example.localhost/index/soap')); // set SOAP service class $server->setClass('Example_Manager'); // register exceptions that generate SOAP faults $server->registerFaultException(array('Example_Exception')); // handle request $server->handle(); } } |
要了解它是如何工做的,能夠嘗試對getProduct()函數發送一個 SOAP 請求,而後給它傳遞一個無效的 ID。清單 16 顯示了一個這樣的 SOAP 請求例子:
<?xml version="1.0" encoding="UTF-8"?> <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="http://example.localhost/index/soap" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"> <env:Body> <ns1:getProduct env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"> <param0 xsi:type="xsd:string">nosuchproduct</param0> </ns1:getProduct> </env:Body> </env:Envelope> |
服務器將驗證輸入,而後發現它是無效的,從而產生一個 Example_Exception,它將會被轉化成一個 SOAP 錯誤,並將它返回給客戶端。清單 17 展現所產生的響應數據包:
<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/> <SOAP-ENV:Body> <SOAP-ENV:Fault> <faultcode>Receiver</faultcode> <faultstring>Invalid input</faultstring> </SOAP-ENV:Fault> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
對於 SOAP 客戶端來講,將這個 SOAP 調用封裝在一個 try-catch 代碼塊中是很好的作法,這樣相似於上面的 SOAP 錯誤就會被順利地捕捉和處理。若是您從新訪問 清單 12 中的示例 SOAP 客戶端,您將看到展現它的實現方法的一個例子。
PHP 的原生 SOAP 擴展的一個缺點是它不支持爲 SOAP 服務自動生成 WSDL 文件。WSDL 文件是頗有用的,由於它們包含了關於可用的 SOAP API 函數的信息,而且能夠被鏈接客戶端用於 「自動發現」 SOAP API。
然而,Zend Framework 包含了一個 Zend_Soap_AutoDiscover 組件,您可使用它爲一個 SOAP 服務自動生成一個 WSDL 文件。它是經過讀取 SOAP 服務類中的 PHPDoc 註釋實現自動生成 WSDL 的。若是您回顧本文以前的清單,您會看到每個函數都帶有 PHPDoc 註釋;這是專門用於簡化 WSDL 自動生成的。
清單 18 展現瞭如何使用 Zend_Soap_AutoDiscover 組件實現 WSDL 自動生成:
<?php class IndexController extends Zend_Controller_Action { public function soapAction() { // disable layouts and renderers $this->getHelper('viewRenderer')->setNoRender(true); // initialize server and set WSDL file location $server = new Zend_Soap_Server('http://example.localhost/index/wsdl'); // set SOAP service class $server->setClass('Example_Manager'); // register exceptions that generate SOAP faults $server->registerFaultException(array('Example_Exception')); // handle request $server->handle(); } public function wsdlAction() { // disable layouts and renderers $this->getHelper('viewRenderer')->setNoRender(true); // set up WSDL auto-discovery $wsdl = new Zend_Soap_AutoDiscover(); // attach SOAP service class $wsdl->setClass('Example_Manager'); // set SOAP action URI $wsdl->setUri('http://example.localhost/index/soap'); // handle request $wsdl->handle(); } } |
清單 18 定義了一個新的wsdlAction(), 它初始化了 Zend_Soap_AutoDiscover 組件的一個實例,而後將它指向 Example_Manager 類。經過調用這個實例的handle()函數,它就會讀取特定的類,解析其中的 PHPDoc 註釋,而後產生一個符合標準的 WSDL 文檔,這個文檔完整地描述了該服務對象。
要看到這個結果,須要在您的瀏覽器上訪問 http://example.localhost/index/wsdl,而後您應該可以看到如 圖 3 所示結果:
如今 SOAP 服務器和客戶端均可以使用這個 WSDL 文件了,而不須要手動指定uri和location參數。清單 18 也說明了這一點,經過修改soapAction(),它將 WSDL URL 傳遞給 Zend_Soap_Server 構造函數,使它以 WSDL 模式啓動。鏈接 SOAP 的客戶端就可以使用這個 WSDL URL 自動發現 SOAP 服務 API 了。