PHP Conference Japan 2024

SoapClient::__doRequest

(PHP 5, PHP 7, PHP 8)

SoapClient::__doRequest執行 SOAP 請求

描述

public SoapClient::__doRequest(
    string $request,
    string $location,
    string $action,
    int $version,
    bool $oneWay = false
): ?string

透過 HTTP 執行 SOAP 請求。

此方法可以在子類別中覆寫,以實作不同的傳輸層、執行額外的 XML 處理或其他用途。

參數

request

XML SOAP 請求。

location

請求的 URL。

action

SOAP 動作。

version

SOAP 版本。

oneWay

如果 oneWay 設定為 true,此方法不會傳回任何內容。當不需要回應時使用此選項。

傳回值

XML SOAP 回應。

更新日誌

版本 描述
8.0.0 oneWay 的類型現在是 bool;之前是 int

範例

範例 1 SoapClient::__doRequest() 範例

<?php

function Add($x, $y)
{
return
$x + $y;
}

class
LocalSoapClient extends SoapClient
{
private
$server;

public function
__construct($wsdl, $options)
{
parent::__construct($wsdl, $options);
$this->server = new SoapServer($wsdl, $options);
$this->server->addFunction('Add');
}

public function
__doRequest(
$request,
$location,
$action,
$version,
$one_way = false,
): ?
string {
ob_start();
$this->server->handle($request);
$response = ob_get_contents();
ob_end_clean();

return
$response;
}
}

$x = new LocalSoapClient(
null,
[
'location' => 'test://',
'uri' => 'http://testuri.org',
]
);

var_dump($x->Add(3, 4));

?>

新增註解

使用者提供的註解 13 則註解

19
tschallacka
8 年前
我只想用簡單的英文記錄如何建立此請求,因為我僅透過誤解參數做出一些假設。

這僅供未來參考(給我自己)以及其他可能因不了解 SOAP 的精妙之處而感到困惑並嘗試學習的人。

$this->__doRequest(string $request , string $location , string $action , int $version [, int $one_way = 0 ] );

$request = XML Soap 信封
$location = WSDL 檔案的 URL。無論您在設定物件時之前如何定義,您都需要在這裡重複使用它。
$action = 要執行的 SOAP 動作。這在 wsdl 檔案中定義,並且可以是單一形式或 URL 的形式。它只是一個參數,可能不是實際有效的 URL
$version = SOAP_1_1 = 內容標頭 (Content-Type: text/xml; charset=utf-8␍)
SOAP_1_2 = 內容標頭 (Content-Type: application/soap+xml; charset=utf-8; action="在 $action 中定義的 somesoapaction")

如果您將 SOAP_1_2 請求傳送到 SOAP_1_1 伺服器,您可能會收到以下形式的回覆

HTTP/1.1 415 無法處理訊息,因為內容類型 'application/soap+xml; charset=utf-8; action="在 $action 中定義的 somesoapaction"' 不是預期的類型 'text/xml; charset=utf-8'。

在這種情況下,您需要切換到 SOAP_1_1 以取得伺服器可以理解的正確格式
33
darren dot yee at emc dot com
11 年前
請注意,當擴充 __doRequest 時,除非您確定更新內部 __last_request 變數,否則呼叫 __getLastRequest 可能會回報不正確的資訊。省去一些麻煩。

function __doRequest($request, $location, $action, $version) {
$request = preg_replace('/abc/', 'def', $request);
$ret = parent::__doRequest($request, $location, $action, $version);
$this->__last_request = $request;
return $ret;
}
13
bwhitehead at tableausoftware dot no dot com dot spam
13 年前
請注意,`SoapClient.__doRequest()` 方法會繞過拋出 `SoapFault` 例外。

具體來說,如果您呼叫 `__doRequest()` 方法且該方法失敗,通常會拋出 `SoapFault` 例外。然而,`__doRequest()` 方法實際上並不會拋出例外。相反地,例外會被儲存在名為 `SoapFault.__soap_fault` 的類別屬性中,並且實際上是在 `__doRequest` 方法完成**後**才拋出(但呼叫堆疊會顯示例外是在 `__doRequest` 方法內部建立的)。

我成功地使用以下程式碼查詢了未拋出的本地快取例外物件

<?php
$exception
= null;
try {
$result = parent::__doRequest($request, $location, $action, $version, $one_way);
}
catch (
SoapFault $sf) {
// 這段程式碼不會執行到
$exception = $sf;
}
catch (
Exception $e) {
// 這段程式碼也不會執行到
$exception = $e;
}
if((isset(
$this->__soap_fault)) && ($this->__soap_fault != null)) {
// 這裡是儲存來自 __doRequest 的例外的地方
$exception = $this->__soap_fault;
}

// 在此決定如何處理例外
// [在此輸入程式碼]
// 或拋出例外
if($exception != null) {
throw
$exception;
}
// 注意:如果您不希望在呼叫堆疊中再次拋出例外,您可能需要取消設定 __soap_fault 的值
?>
14
albert at jool dot nl
17 年前
如果您想使用預設設定的 ASP.NET 伺服器進行 SOAP 1.1 通訊,請使用以下程式碼覆寫您的 `__doRequest`。調整命名空間參數,一切就可以正常運作了。

<?php
class MSSoapClient extends SoapClient {

function
__doRequest($request, $location, $action, $version) {
$namespace = "http://tempuri.com";

$request = preg_replace('/<ns1:(\w+)/', '<$1 xmlns="'.$namespace.'"', $request, 1);
$request = preg_replace('/<ns1:(\w+)/', '<$1', $request);
$request = str_replace(array('/ns1:', 'xmlns:ns1="'.$namespace.'"'), array('/', ''), $request);

// 父類別呼叫
return parent::__doRequest($request, $location, $action, $version);
}
}

$client = new MSSoapClient(...);
?>

希望這能為人們省下無數的調整時間...
8
jfitz at spacelink dot com
18 年前
請注意,`__getLastRequest()` 資料是在呼叫 `__doRequest()` **之前**就已緩衝的。因此,您在 `__doRequest()` 中對 XML 所做的任何修改都將不會在 `__getLastRequest()` 的輸出中顯示。至少在 v5.2.0 版本中是這樣。
3
lepidosteus
15 年前
如果您在請求期間遇到錯誤,顯示「SOAP-ERROR: Encoding: Can't decode apache map, only Strings or Longs are allowd as keys」,原因似乎是回應 XML 使用整數作為鍵,而 PHP 無法理解它們。

這是對我來說有效的方法(將整數鍵轉換為字串):

<?php
class mySoap extends SoapClient
{
public function
__doRequest($request, $location, $action, $version)
{
$result = parent::__doRequest($request, $location, $action, $version);
$result = str_replace('<key xsi:type="xsd:int">', '<key xsi:type="xsd:string">', $result);
return
$result;
}
}

// $soap = new mySoap(...
?>
2
tbernard at qcsupply dot com
9 年前
如果您在連線到已驗證的 SOAP 服務時遇到問題,這裡有一些重要的注意事項。

`__doRequest()` 僅在呼叫 `SOAPClient` 函式時使用,**而不是**在擷取和解析 WSDL 時使用。這表示如果您的 WSDL 檔案不是公開可存取的,而是位於驗證後面,則預設情況下將無法存取。相反地,您必須建立一個重載的串流封裝器,並針對您將使用的任何協定(可能是 HTTP)進行註冊。
1
Artur Graniszewski
15 年前
請注意 `__doRequest()` 方法中 PHP 不一致的行為。傳遞給此方法的某些參數似乎是以傳址方式傳遞的!

如果您嘗試建立自己的 `__doRequest()` 方法並將其引數儲存為 `SoapClient` 屬性,您會發現所有引數在 `__soapCall` 之後都會變成 `null` 或未知。

<?php
protected $__soapAction = '';

public function
__doRequest($request, $location, $action, $version, $oneWay = 0) {
ob_start();
$this->server->handle($request);
$response = ob_get_contents();
ob_end_clean();
$this->__soapAction = $action;
return
$response;
}
?>

在上面的範例中,`$this->__soapAction` 在 `$obj->__soapCall()` 之後會是 `null`。

要儲存 `$action` 的值,您必須將其轉換為字串(這樣 PHP 會被迫建立一個具有不同記憶體指標的新變數)。

<?php
public function __doRequest($request, $location, $action, $version, $oneWay = 0) {
ob_start();
$this->server->handle($request);
$response = ob_get_contents();
ob_end_clean();
$this->__soapAction = (string)$action;
return
$response;
}
?>
1
psfere at hotmail dot com
14 年前
我需要新增一個空白的 SOAP 標頭 (`<SOAP-ENV:Header />`),但找不到其他地方有這麼做。我唯一能夠支援此操作的方法是擴展 `SoapClient` 並重新定義 `__doRequest`。希望這對某些人有所幫助,或者如果程式庫中支援此功能,請指引我正確的方向。

<?php
class MySoapCli extends SoapClient {
function
__doRequest($request, $location, $action, $version) {
$dom = new DomDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$dom->loadXML($request);
$hdr = $dom->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'SOAP-ENV:Header');
$dom->documentElement->insertBefore($hdr, $dom->documentElement->firstChild);
$request = $dom->saveXML();
return
parent::__doRequest($request, $location, $action, $version);
}
}
?>
0
alireza dot meskin at gmail
12 年前
變更 Socket 串流的阻擋模式並設定 SOAP 請求的逾時時間

<?php

class TimeoutSoapClient extends SoapClient
{
const
TIMEOUT = 20;
public function
__doRequest($request, $location, $action, $version, $one_way = 0)
{
$url_parts = parse_url($location);
$host = $url_parts['host'];
$http_req = 'POST '.$location.' HTTP/1.0'."\r\n";
$http_req .= 'Host: '.$host."\r\n";
$http_req .= 'SoapAction: '.$action."\r\n";
$http_req .= "\r\n";
$http_req .= $request;
$port = 80;
if (
$url_parts['scheme'] == 'https')
{
$port = 443;
$host = 'ssl://'.$host;
}
$socket = fsockopen($host, $port);
fwrite($socket, $request);
stream_set_blocking($socket, false);
$response = '';
$stop = microtime(true) + self::TIMEOUT;
while (!
feof($socket))
{
$response .= fread($socket, 2000);
if (
microtime(true) > $stop)
{
throw new
SoapFault('Client', 'HTTP timeout');
}
}
return
$response;
}
}
0
metator at netcabo dot pt
19 年前
如有必要,您可以使用此方法來修正傳送前的 SOAP 請求。您可以使用 DOM API 來完成此操作。

<?php

public ExtendedClient extends SoapClient {

function
__construct($wsdl, $options = null) {
parent::__construct($wsdl, $options);
}

function
__doRequest($request, $location, $action, $version) {
$dom = new DOMDocument('1.0');

try {

//將 SOAP 請求載入至文件
$dom->loadXML($request);

} catch (
DOMException $e) {
die(
'解析錯誤,代碼為 ' . $e->code);
}

//建立 XPath 物件來查詢請求
$path = new DOMXPath($dom);

//搜尋節點
$nodesToFix = $path->query('//SOAP-ENV:Envelope/SOAP-ENV:Body/path/to/node');

//檢查節點是否正常
$this->checkNodes($path, $nodesToFix);

//儲存已修改的 SOAP 請求
$request = $dom->saveXML();

//doRequest
return parent::__doRequest($request, $location, $action, $version);
}

function
checkNodes(DOMXPath $path, DOMNodeList $nodes) {
//逐一查看節點清單
for ($i = 0; $ < $nodes->length; $i++) {
$aNode = $nodes->item($i);

//這只是一個範例
if ($node->nodeValue == null) {
//執行某些操作。例如,讓我們移除它。
$node->parentNode->removeChild($node);
}
}
}
}
?>

這讓開發人員有機會解決 Web 服務的互通性問題。
-1
Anonymous
18 年前
當您需要將請求傳送至具有 ComplexType 參數的服務時,您的 PHP5 SoapClient 是否有問題?

也許是因為我的服務是用 Delphi 和 REMObjects SDK 3.0 建置的,所以我遇到了這些問題,也許不是。無論如何,這就是我的解決之道
<?php
$versie
= new stdClass();//定義一個基本的類別物件
$versie->versieID = $aVersie->versieID();//使用與您在 WSDL 中 complextype 物件完全相同的屬性來填充它
$versie->versieNummer = $aVersie->versieNummer();
$versie->isActief = $aVersie->isActief();

$soapVersieType = new SoapVar($versie , SOAP_ENC_OBJECT, "Versie", "http://127.0.0.1:8999/SOAP?wsdl"); //建立複雜的 SOAP 類型,Versie 是我在 WSDL 中複合類型的名稱,後面的 URL 是我的 WSDL 的位置。

try{
$result = $soapClient->BewaarVersie($this->sessieId,$soapVersieType); //BewaarVersie 是從我的 WSDL 衍生出來的函數,帶有兩個參數。
}
catch(
SoapFault $e){
trigger_error('SOAP 發生錯誤:'.$e->faultstring,E_USER_WARNING); }
?>

經過更多測試後,我發現不需要轉換為 StdClass() 物件。我的 'Versie' 本地物件具有針對 'Versie' WSDL 複合類型定義為私有變數的屬性,並且當我使用本地 'Versie' 物件的實例建立 SoapVar 時,不會有任何問題。
-1
james dot ellis at gmail dot com
16 年前
如果您的應用程式與 SOAP 服務互動,並且您希望快取回應以供稍後使用,那麼覆寫 SoapClient::__doRequest 是可行的方法。

例如,如果您知道呈現的資訊不會經常變更,而且您不希望執行多餘的 HTTP 請求,您可以從本機快取擷取回應,並讓 SoapClient 執行轉換為 PHP 資料類型。

<?php
class YourNamespace_SoapClient_Local extends SoapClient {
protected
$cacheDocument = "";
public function
__construct($wsdl, $options) {
parent::__construct($wsdl, $options);
}

/**
* SetCacheDocument() 設定先前快取的檔案內容
*/
public function SetCacheDocument($document) {
$this->cacheDocument = $document;
}

/**
* __doRequest() 覆寫標準 SoapClient 以處理本機請求
*/
public function __doRequest() {
return
$this->cacheDocument;
}
}

//---- 顯示在類別中使用方式的程式碼片段
//$document 是先前請求快取的 SOAP 回應檔案,使用 SoapClient::__getLastResponse() 儲存到某處的快取中
//為此範例的目的,假設已設定 $this->wsdl、$this->options、$this->method 和 $this->params。

public function SoapRequest($document) {
$method = $this->method;
if(
$document == "") {
//未快取
try {
//預設選項
$client = new SoapClient($this->wsdl, $this->options);
$result = $client->$method($this->params);
//將回應傳送到快取
$this->CacheResponse($client->__getLastResponse());
} catch(
SoapFault $fault) {
//記錄某些內容
return FALSE;
}
} else {
//快取的檔案
try {
/**
* 需要設定 WSDL,才能允許在用戶端物件上呼叫方法,
* 並觸發 SoapClient 將回應解碼為原生資料類型
*/
$client = new YourNamespace_SoapClient_Local($this->wsdl, $this->options);
$client->SetCacheDocument($document);
$result = $client->$method($this->params);
} catch (
SoapFault $fault) {
//記錄某些內容
return FALSE;
}
}
return
$result;
}
?>

我將讓您自行處理快取,那裡有很多選項.. ;)
To Top