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;

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

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 个注释

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="somesoapaction 定义在 $action 中")

如果您向 SOAP_1_1 服务器发送 SOAP_1_2 请求,您可能会收到以下形式的回复

HTTP/1.1 415 无法处理消息,因为内容类型“application/soap+xml; charset=utf-8; action="somesoapaction 定义在 $action 中"”不是预期的类型“text/xml; charset=utf-8”。

在这种情况下,您需要切换到 SOAP_1_1 以获取服务器可以理解的正确格式
darren dot yee at emc dot com
11 年前
请注意,在扩展 __doRequest 时,调用 __getLastRequest 可能会报告不正确的信息,除非您确保更新内部 __last_request 变量。省去一些麻烦。

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;
}
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 值
?>
albert at jool dot nl
17 年前
如果您想与具有 SOAP 1.1 支持的默认配置的 ASP.NET 服务器通信,请使用以下代码覆盖您的 __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(...);
?>

希望这能为人们节省无数个小时的折腾...
jfitz at spacelink dot com
17 年前
请注意,__getLastRequest() 数据在调用 __doRequest() 之前被缓冲。因此,您在 __doRequest() 中对 XML 所做的任何修改都不会在 __getLastRequest() 的输出中显示。这至少在 v5.2.0 中就是这样。
lepidosteus
14 年前
如果您在请求期间遇到错误,提示“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(...
?>
tbernard at qcsupply dot com
9 年前
如果您在连接到经过身份验证的 SOAP 服务时遇到问题,这里需要注意一些重要的内容。

__doRequest() 仅在调用 SOAPClient 函数时使用,而不是在获取和解析 WSDL 时使用。这意味着,如果您的 WSDL 文件不可公开访问,而且也位于您的身份验证之后,则默认情况下无法访问它。相反,您必须创建一个重载的流包装器,并将其注册到您将使用的任何协议(可能是 HTTP)上。
Artur Graniszewski
14 年前
请注意 __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;
}
?>
psfere at hotmail dot com
13 年前
我需要添加一个空白的 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);
}
}
?>
alireza dot meskin at gmail
12 年前
更改套接字流的阻塞模式并设置 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;
}
}
metator at netcabo dot pt
18 年前
如果需要,可以使用此方法在发送 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();

// 发出请求
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 服务的互操作性问题。
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 时不会出现任何问题。
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