請注意,如果您需要設定 SOAP 請求的逾時時間,您可以使用 ini_set 來變更 default_socket_timeout 的值。我之前使用過 NuSOAP,它的 SOAP 客戶端類別有一個逾時選項,而我花了一段時間才弄清楚 PHP 的 SOAP 使用與其他所有內容相同的 Socket 選項。
請注意,如果您需要設定 SOAP 請求的逾時時間,您可以使用 ini_set 來變更 default_socket_timeout 的值。我之前使用過 NuSOAP,它的 SOAP 客戶端類別有一個逾時選項,而我花了一段時間才弄清楚 PHP 的 SOAP 使用與其他所有內容相同的 Socket 選項。
若要使用 SoapServer()、WSDL 檔案和 Zend Studio 客戶端/伺服器偵錯 SOAP 服務,您必須將 ?start_debug=1&debug_port=10000 附加到服務位置
--- snip ---
... 方法/服務定義 ....
<service name="SOAPService">
<port
name="SOAPServicePort"
binding="typens:SOAPServiceBinding">
<soap:address
location="$URL?start_debug=1&debug_port=10000"/>
</port>
</service>
--- snap ---
是否在 WSDL 模式下使用 PHP SoapServer 時,在 SOAP 上傳遞複雜類型時遇到問題?是否無法正確解碼?這可能是您正在尋找的解決方案!
當在 WSDL 檔案的 schema 部分中使用 ComplexType 時,您需要額外一個步驟來告訴 PHP SOAP 如何編碼物件。第一種方法是明確地將物件封裝在 SoapVar 物件中 - 告訴 PHP 使用一般化的 SOAP 編碼規則(將所有 ComplexType 編碼為 Struct)。但是,如果用戶端期望根據 WSDL 的 schema 編碼物件,則此方法將無法運作。因此,實際的做法是
* 首先,定義一個特定的 PHP 類別,它實際上只是一個保存各種屬性的資料結構,以及 WSDL 中適當的 ComplexType。
<?php
class MyComplexDataType {
public $myProperty1;
public $myProperty2;
}
?>
<complexType name="MyWSDLStructure">
<sequence>
<element name="MyProperty1" type="xsd:integer"/>
<element name="MyProperty2" type="xsd:string"/>
</sequence>
</complexType>
* 接下來,在初始化 SoapServer 時告訴它將這兩個結構對應在一起。
<?php
$classmap = array('MyWSDLStructure' => 'MyComplexDataType');
$server = new SoapServer("http://MyServer/MyService.wsdl", array('classmap' => $classmap))
?>
* 最後,讓您的方法直接傳回類別的實例,並讓 SoapServer 處理編碼!
<?php
public function MySoapCall() {
$o = new MyComplexDataType();
$o->myProperty1 = 1;
$o->myProperty2 = "MyString";
return $o
}
?>
當使用 wsdl 和 flash soap 與 php 時
(不完全是 php,而是適當的 wsdl - 我花了數小時才弄清楚如何在 flash 網路服務中正常運作)
當命名定義時,
不要使用 "tns:",而是使用 "typens:"
請確定您的 "definitions/targetNamespace"
與您的 "soap:body" 命名空間相同
範例:"urn:mynamespace"
請確定您的 "binding/type" 是使用 "typens" 宣告的
請確定您的 service/port/binding 設定為 'typens:...'
如果您沒有正確執行,您最終會得到
WSDL.UnrecognizedNamespace - 在 flash 中
但在 php soapclient 中似乎正常...
快樂編碼:)
希望這能幫助您節省 WSDL 的時間:)
如果您不想手動維護 classmap,請確保您的 PHP 物件類別名稱和 WSDL complexTypes 的名稱相同,並使用以下程式碼
$classmap = array();
$tmpClient = new SoapClient("soapserver.wsdl");
foreach($tmpClient->__getTypes() as $type)
{
$array = split(" ", $type);
if($array[0] == "struct" && class_exists($array[1]))
{
$classmap[$array[1]] = $array[1];
}
}
unset($tmpClient);
$server = new SoapServer("soapserver.wsdl", array("classmap" => $classmap));
unset($classmap);
$server->setClass("someclass");
$server->handle();
我希望這能為某人節省時間。當開發和測試您的 SOAP 伺服器時,請記住要停用用戶端和伺服器中的 WSDL 快取
$ini = ini_set("soap.wsdl_cache_enabled", 0);
如果您正在工作
- 在 localhost 上
- 在 WSDL 模式中
而且當您嘗試將 SOAP 用戶端與 SOAP 伺服器連線時遇到以下錯誤
Fatal error: Uncaught SoapFault exception: [HTTP] Could not connect to host...
然後將 WSDL 檔案中的 "localhost" 變更為 "127.0.0.1"
<soap:address location='http://127.0.0.1/soap-server.php'/>
wokan at cox dot net 對於將 URI 傳遞給 HTTPS URI 的值的安全性說法不正確。HTTPS 連線是 SSL 內部的 HTTP -- 所有 HTTP 流量,包括請求,都會加密。
如果您對 NuSOAP 在 PHP 5.x 上無法運作感到困惑,原因是此內建 SOAP 擴充功能使用與 Nusoap 相同的 soapclient() 類別名稱。
將 nusoap.php 中的 'soapclient' 取代為 'soapclient_xxx',一切就沒問題了...
當遇到像這樣的錯誤訊息時
[faultstring] => 函數 ("yourMethod") 不是此服務的有效方法
即使它存在於 WSDL 等中,請注意 PHP 會在本地快取 wsdl 以提高效能。您可以透過 php.ini/.htaccess 完全停用快取,或移除快取檔案 (/tmp/wsdl-.. 如果您使用的是 Linux) 以強制重新產生。
注意使用 PHP Soap + 會期 + PEAR DB 類別的人。
每次您透過 SOAP 用戶端對您的網路服務進行呼叫時,您的 PEAR DB 會期就會進入休眠狀態,而且預設情況下它不會在下一個請求時喚醒。
為了修正這個問題,我只是在我的 $soap->handle(); 之下呼叫了我特定的資料庫關閉呼叫 ifx_close()
給在 Windows 環境下使用憑證進行 SOAP 通訊的人一個注意事項:似乎必須提供憑證檔案的完整路徑,而且不要在路徑前面加上 'file://'。
<?php
$wsdl = "test.wsdl";
$local_cert = "c:\htdocs\mycert.pem";
$passphrase = "xyz";
$client = new SoapClient($wsdl, array('local_cert' => $local_cert, 'passphrase' => $passphrase));
?>
哇,這程式真棒,而且 SOAP 對我來說是個新東西。
我發現了一些有趣的事情,但我無法除錯,因為腳本在沒有任何錯誤訊息或提示的情況下就結束了。 :-(
你可能會遇到記憶體問題,尤其是在伺服器負載很高時的「共享伺服器」上。
有時候腳本會正常運作,有時候則會在不明的地方停止。
以下是我的腳本執行的步驟
* 從遠端伺服器取得資料(約 4.5 MB)
* 解析請求的物件,並將資料儲存到資料庫中。
帶有除錯訊息的回傳結果很有趣
-> 檢查記憶體限制:30M
-> $client = new new SoapClient($url_wsdl, $options);
-> 記憶體使用量:185888
-> $client->[請求的取得資料方法]();
-> 檢查:__getLastResponseHeaders() - 之後
-> HTTP/1.1 200 OK // 遠端伺服器對我來說沒問題 :-)
-> Content-Length: 4586742 // 我取得資料了
-> 檢查:現在的記憶體使用量:23098872 // 哇!這不可能吧!!
所以,如果現在伺服器上的某個東西佔用了剩下的記憶體,則處理資料的過程就會中斷 :-(
因此,我需要儲存 XML 樹 ($client->client->__last_response) 並以傳統的方式解析它。(如果你請求更多記憶體,如果更頻繁地執行類似這樣的腳本,你可能會在共享伺服器上與管理員發生衝突!)
這可能會很有幫助,因為我花了一些時間才發現這個問題
如果你使用某個 .wsdl,並且有一個序列可以出現多次(例如:maxOccurs > 1),如果有多個項目,你可以為它指定一個非關聯陣列;或者如果只有一個項目,你可以只指定該項目
<?php
'items' => array(
array(
'itemId' => 5,
'name' => 'some name',
),
array(
'itemId' => 6,
'name' => 'some other name',
),
),
?>
這也適用
<?php
'items' => array(
'itemId' => 5,
'name' => 'some name',
),
?>
這裡有 73 個測試案例,詳細說明了 PHP5 目前支援的架構類型,以及您應該將哪些結構插入其中以取得服務的傳回值。這比嘗試猜測好多了!
http://cvs.php.net/co.php/pecl/soap/tests/schema
您可以透過變更下方 URL 中的主要索引來瀏覽清單,而無需進出頁面
http://cvs.php.net/co.php/pecl/soap/tests/schema/schema052.phpt?r=1.2
我用這個下載了整個東西,CVS 也許也可以。
http://www.httrack.com/
下載完成後,我只是用 Textpad 來瀏覽它們。
想知道為什麼您剛添加到 WSDL 檔案中的函式無法在您的 SOAP 用戶端中使用嗎?關閉 WSDL 快取,(如文件所述)預設為開啟。
在您的腳本頂部,使用
$ini = ini_set("soap.wsdl_cache_enabled","0");
傳輸包含物件或物件陣列的物件時,PHP5 底下的 SOAP 擴充功能存在問題。巢狀物件將無法傳輸。
解決方案
這個類別是我透過反覆試驗開發出來的。因此,這 23 行程式碼解決了大多數在 PHP5 下編寫程式碼的開發人員使用 SOAP 擴充功能的問題。
<?php
/*
根據 PHP5 中 SOAP 類別的特定組織處理方式,我們必須將複雜的物件包裝在 SoapVar 類別中。否則,物件將無法正確編碼,也無法在遠端 SOAP 處理常式上載入。
函式 "getAsSoap" 會呼叫以編碼物件以進行傳輸。編碼後,它可以正確傳輸。
*/
abstract class SOAPable {
public function getAsSOAP() {
foreach($this as $key=>&$value) {
$this->prepareSOAPrecursive($this->$key);
}
return $this;
}
private function prepareSOAPrecursive(&$element) {
if(is_array($element)) {
foreach($element as $key=>&$val) {
$this->prepareSOAPrecursive($val);
}
$element=new SoapVar($element,SOAP_ENC_ARRAY);
}elseif(is_object($element)) {
if($element instanceof SOAPable) {
$element->getAsSOAP();
}
$element=new SoapVar($element,SOAP_ENC_OBJECT);
}
}
}
// ------------------------------------------
// 抽象範例
// ------------------------------------------
class PersonList extends SOAPable {
protected $ArrayOfPerson; // 變數必須是 protected 或 public!
}
class Person extends SOAPable {
//任何資料
}
$client=new SoapClient("test.wsdl", array( 'soap_version'=>SOAP_1_2, 'trace'=>1, 'classmap' => array('Person' => "Person", 'PersonList' => "PersonList") ));
$PersonList=new PersonList;
// 一些操作
$PersonList->getAsSOAP();
$client->someMethod($PersonList);
?>
因此,每個將透過 SOAP 傳輸的類別都必須繼承自 SOAPable 類別。
如您在上面的程式碼中所見,函式 prepareSOAPrecursive 會在父物件或陣列中搜尋另一個巢狀物件,如果找到,則會嘗試呼叫函式 getAsSOAP() 以準備巢狀物件,之後再透過 SoapVar 類別簡單地包裝。
因此,在傳輸之前的程式碼中,只需呼叫 $obj->getAsSOAP()
關於「無法辨識 DTD...」錯誤的注意事項。請檢查您的 wsdl 檔案是否包含
<?xml version ='1.0' encoding ='UTF-8' ?>
另請確保您使用服務的完整路徑(在 wsdl、用戶端和伺服器中)
...
<wsdlsoap:address location='http://www.mysite.com.au/web_services/myserver.php' />
...
<?php
// SOAP 伺服器
$server = new SoapServer('http://www.mysite.com.au/web_services/hello.wsdl');
...
...
?>
<?php
// SOAP 用戶端
$client = new SoapClient('http://www.mysite.com.au/web_services/hello.wsdl');
...
...
?>
僅供參考,我不是 SOAP 專家,但我希望這對某些人有幫助 ;)
在使用第三方服務時,我遇到了:「SOAP-ERROR:解析架構:限制中出現意外的 <text>」
我認為這值得分享。它的意思是說在無效的位置有一些文字。C# 似乎會忽略它,如果您使用 nusoap,它也不會注意到。但導致我問題的原因是類似
<types>
<schema ...
<simpleType name="some type">
<restriction base="xsd:string">
<enumeration value="foo"/>;
</restriction>
</simpleType>
請注意分號 (;)。將其過濾掉即可。
如果你使用 wsdl,
請確保你正確定義了輸入。
如果你的方法不包含任何輸入參數,
你必須確保你
- 不要為輸入建立 message 標籤。
- 不要將輸入放在 porttype / operation 內。
- 不要將輸入放在 binding / operation 內。
否則,你會收到錯誤訊息
[Client] 看起來我們沒有收到 XML
該死,我花了好幾個小時才搞清楚...
對於那些使用充滿複雜型別的 wsdl,只想得到一個類別結構來掛載你的程式碼,而不必擔心輸入長長的參數列表(或建立腳本來執行此操作)的人來說:wsdl2php 是一個很棒的省時工具。它可以建立一個結構,讓你可以進入並加入所需的驗證和特殊資料處理:http://www.urdalen.no/wsdl2php/
感謝你,Knut。
如果你使用 SSL 搭配憑證和密碼驗證
$wsdl = "https://ws.ecopatz.de/ProductInfo?wsdl";
$pass = '一個密碼';
$certFile = "./mycert.pem";
$client = new SoapClient($wsdl,
array(
'local_cert' => $certFile,
'passphrase' => $pass
)
);
如果你在憑證檔案方面遇到問題,例如這樣
Warning: SoapClient::__construct(): 無法設定本機憑證鏈檔案 `./mycert.pem';檢查你的 cafile/capath 設定是否包含你的憑證及其發行者的詳細資訊,位於 productinfo.php 的第 27 行
那麼憑證檔案可能採用「錯誤的格式」(可能是 php 的錯誤格式)。當我將私鑰檔案和憑證檔案的內容附加到單個檔案「mycert.pem」時,它對我有效
cat mycert.key >mycert.pem # mycert.key 是私鑰
cat mycert.crt >>mycert.pem # mycert.crt 是已簽署的憑證
感謝某個作者,他指出了「curl --cert」,其中提到了這個不太重要的依賴項。
如果你想為 Microsoft Office 的用戶端(例如 Microsoft Office Research Service)建立一個 SOAP 伺服器,你需要重寫 SOAP 的命名空間
<?php
// (...)
$server = new SoapServer($wsdl, array('uri' => $uri, 'classmap' => $classmap));
$server->setClass($class);
function callback($buffer)
{
$s = array('<ns1:RegistrationResponse>', 'ns1:', 'xmlns:ns1="urn:Microsoft.Search"');
$r = array('<RegistrationResponse xmlns="urn:Microsoft.Search">', '', '');
return (str_replace($s, $r, $buffer));
}
ob_start('callback');
$server->handle();
ob_end_flush();
// (...)
?>
在這個 URL 有一個完整的範例:http://touv.ouvaton.org/article.php3?id_article=104
這裡有一個如何傳遞 ArrayOfAnyType 引數的範例
包含複雜型別。
假設你的 WSDL 檔案將「http://any.url.com/」定義為預設命名空間和一個複雜型別「SomeComplexType」。
如果你想呼叫一個接受「SomeComplexType」的 ArrayOfAnyType 引數的 WebServices,你需要執行以下操作
<?php
// complexTypes 是一個包含多個 SomeComplexType 實例的陣列
myWSParameter = array();
foreach (complexTypes as ct)
{
// 不要拼錯型別或命名空間。另請注意,php 不會採用 WSDL 檔案中定義的預設命名空間。
myWSParameter []= new SoapVar(ct, 0, "SomeComplexType", "http://any.url.com/");
}
?>
另一方面,當 WebService 回傳 ArrayOfAnyType 時,你必須執行以下操作才能存取它的每個元素。
<?php
// 在這裡,我們將輸出每個回傳項目
$res = $someWS->myFunction($myArgs)
// 如果只回傳一個元素,則不會建立陣列
if (is_array(myFunctionResult->anyType))
{
foreach (myFunctionResult->anyType as $soapVar)
{
echo $soapVar->enc_value;
}
}
else
{
echo myFunctionResult->anyType->enc_value;
}
?>
這一切都已使用 .NET WebService 進行測試。
如果你嘗試使用 SOAP 擴充功能透過 SSL 和自訂 PEM 檔案,你需要執行此操作
$client->_local_cert = "C:\\path\myCert.pem";
如果你在呼叫 .NET Web 服務時遇到問題,請參閱 https://php.dev.org.tw/soap_soapclient_soapcall(__soapCall 方法)上的註解。
在 URI 中傳遞使用者名稱和密碼並不是良好的安全實務,因為 SSL 的目的是防止該資訊被攔截。將該資訊放入 URI 會使其可以被攔截。HTTPS-Post 的值是安全的,因為在 SSL 交握完成後,標頭中傳遞的值才會被傳送。
我花了一些時間才在 windows/apache1.3 上透過 https 正確建立受密碼保護的用戶端連線。這是我的小指南
1. SOAP 擴充功能預設未啟用 (PHP5 RC1)。只需將「extension=php_soap.dll」加入 php.ini,並且不要忘記正確設定 extension_dir(在大多數情況下為「c:\php\ext」)。
2. 將「extension=php_openssl.dll」加入 php.ini。此模組依賴 libeay32.dll 和 ssleay32.dll - 將它們從 php 資料夾複製到 system32 資料夾。
3. 重新啟動 apache
4. 原始碼
$client = new SoapClient("https://yourLogin:yourPassword@foo.com/bar.wsdl", array(
"login" => "yourLogin",
"password" => "yourPassword",
"trace" => 1,
"exceptions" => 0));
$client->yourFunction();
print "<pre>\n";
print "Request: \n".htmlspecialchars($client->__getLastRequest()) ."\n";
print "Response: \n".htmlspecialchars($client->__getLastResponse())."\n";
print "</pre>";
目前似乎有必要將你的登入和密碼都加入 URI 和選項陣列中。不確定這是否為預期行為。
我的電腦上有 PHP 5.2.5。我得到這個結果
陣列
(
[0] => stdClass Object
(
[Client] => stdClass Object
(
[Nom] => LeNom:00000
[Prenom] => LePrenom:00000
)
)
我的主機有 5.1.6,相同的呼叫會回傳這個結果
陣列
(
[0] => stdClass Object
(
[Client] => 陣列
(
[0] => stdClass Object
(
[Nom] => LeNom:00000
[Prenom] => LePrenom:00000
)
5.2.5 是良好的結構。
請注意你使用的版本。更改所有程式碼可能需要大量的工作
對於那些想知道如何在 PHP5 SOAP 中設定節點屬性的人,應該這樣做
<... soap env/header>
<foo bar="blah">12345</foo>
array("foo" => array("_" => 12345, "bar" => "blah"));