PHP Conference Japan 2024

SoapClient::__soapCall

(PHP 5, PHP 7, PHP 8)

SoapClient::__soapCall调用 SOAP 函数

描述

public SoapClient::__soapCall(
    字符串 $name,
    数组 $args,
    ?数组 $options = null,
    SoapHeader|数组|null $inputHeaders = null,
    数组 &$outputHeaders = null
): 混合

这是一个低级别 API 函数,用于进行 SOAP 调用。通常,在 WSDL 模式下,SOAP 函数可以作为 SoapClient 对象的方法来调用。此方法在非 WSDL 模式下很有用,此时 soapaction 未知,uri 与默认值不同,或者在发送和/或接收 SOAP 头时。

发生错误时,对 SOAP 函数的调用可能会导致 PHP 抛出异常,或者如果禁用异常则返回 SoapFault 对象。要检查函数调用是否失败以捕获 SoapFault 异常,请使用 is_soap_fault() 检查结果。

参数

name

要调用的 SOAP 函数的名称。

args

传递给函数的参数数组。这可以是有序数组或关联数组。请注意,大多数 SOAP 服务器都需要提供参数名称,在这种情况下,这必须是关联数组。

options

传递给客户端的关联数组选项。

location 选项是远程 Web 服务的 URL。

uri 选项是 SOAP 服务的目标命名空间。

soapaction 选项是要调用的操作。

inputHeaders

要与 SOAP 请求一起发送的头数组。

outputHeaders

如果提供,此数组将填充 SOAP 响应中的头。

返回值

SOAP 函数可能返回一个或多个值。如果 SOAP 函数只返回一个值,则返回值将是标量。如果返回多个值,则会返回命名输出参数的关联数组。

发生错误时,如果 SoapClient 对象是在将 exceptions 选项设置为 false 的情况下构造的,则将返回 SoapFault 对象。

示例

示例 #1 SoapClient::__soapCall() 示例

<?php

$client
= new SoapClient("some.wsdl");
$client->SomeFunction($a, $b, $c);

$client->__soapCall("SomeFunction", array($a, $b, $c));
$client->__soapCall("SomeFunction", array($a, $b, $c), NULL,
new
SoapHeader(), $output_headers);


$client = new SoapClient(null, array('location' => "https://127.0.0.1/soap.php",
'uri' => "http://test-uri/"));
$client->SomeFunction($a, $b, $c);
$client->__soapCall("SomeFunction", array($a, $b, $c));
$client->__soapCall("SomeFunction", array($a, $b, $c),
array(
'soapaction' => 'some_action',
'uri' => 'some_uri'));
?>

参见

添加注释

用户贡献的注释 20 条注释

vb
12 年前
请注意,调用 __soapCall 和从 WSDL 调用生成的方法需要以两种不同的方式指定参数。

例如,如果您有一个具有采用用户名和密码的 login 方法的 Web 服务,则可以按以下方式调用它
<?php
$params
= array('username'=>'name', 'password'=>'secret');
$client->login($params);
?>

如果要调用 __soapCall,则必须将参数包装在另一个数组中,如下所示
<?php
$client
->__soapCall('login', array($params));
?>
arturklesun at gmail dot com
8 年前
分享我的经验,因为我相信如果您决定使用此 Soap Client 实现,这将对您至关重要。

在 php 7.0.8 中,SoapClient 从响应生成的 stdClass 不使用“minOccurs”和“maxOccurs”WSDL 修饰符来区分 stdClass 中的属性(即“关联数组”中的键)和数组中的元素(即“索引数组”)。

相反,实现方式是通过序列中是否存在只有一个具有该标签的元素还是多个元素来简单地决定标签是_关联数组中的键_还是_索引数组中具有相同标签的元素之一_。

考虑一下我案例中的一个实际例子
<?php
response xml
:
...
<
ValidatingCarrier>
<
Alternate>AA</Alternate>
</
ValidatingCarrier>
...
<
ValidatingCarrier>
<
Alternate>AA</Alternate>
<
Alternate>AY</Alternate>
</
ValidatingCarrier>
...

SoapClient生成的响应结构:
...
[
ValidatingCarrier] => stdClass Object(
[
Alternate] => AA // 这里是一个字符串
)
...
[
ValidatingCarrier] => stdClass Object (
[
Alternate] => Array ( // 这里是一个数组
[0] => AA
[1] => AY
)
)
...

字段XSD定义:
<
xs:element name="Alternate" type="CarrierCode" minOccurs="0" maxOccurs="24">
?>

你看,XSD中的定义告诉我们这个标签可以重复最多24次,这意味着它将是一个索引数组,但是SoapClient没有考虑到这一点,并将示例中的第一个<Alternate>视为值而不是包含该值的数组。

毫无疑问,只有当maxOccurs为1或更小或未指定(默认值为1,参见https://www.w3.org/TR/xmlschema-0/#OccurrenceConstraints)时,值才应该是stdClass中的属性(关联数组中的键)。希望这些信息在你实现你自己的、正确工作的SoapClient时对你有所帮助。
Shto
15年前
需要注意的一点。

我遇到过这个问题,并且花了一段时间才发现问题所在。

我试图从提供的Web服务获取.NET对象,但是它总是返回空对象。它确实返回了主干,但是没有构成结构的任何对象。

无论如何,在调用这些函数时,你必须对数组非常精确。例如,这样做

<?php
$obj
= $client->__soapCall($SOAPCall, array('parameters'=>$SoapCallParameters));
?>

这意味着你必须使用'parameters'作为键,并将soap调用参数作为值,传入一个数组作为第二个参数。

还要确保参数变量(在我的例子中是$SoapCallParameters)采用Web服务请求的格式。

所以,不要仅仅创建一个这样的数组
<?php

(
[
0] => 'Mary',
[
1] => 1983
)

?>

但是如果Web服务请求'muid'变量为'Mary','birthyear'为1983,那么请像这样创建你的数组

<?php

(
[
muid] => 'Mary',
[
birthyear] => 1983
)

?>

上面的数组指的是$SoapCallParameters变量。

希望这能帮助某些人,避免花费太多时间找出问题。
DesmondJ
19年前
遵循OrionI的例子

<?php
$client
= new SoapClient("http://server/sumservice.asmx?WSDL");
$params->a = 2;
$params->b = 3;
$objectresult = $client->Sum($params);
$simpleresult = $objectresult->SumResult;
print(
$simpleresult); // 输出 "-1"
?>

请注意以下几行

"$client->Sum($params);"



"$simpleresult = $objectresult->SumResult;"

是相互关联的。如果你的Web服务函数被称为"Sum",则在其末尾添加"Result"以获取调用的结果。

例如

<?php
$client
= new SoapClient("http://server/mathservice.asmx?WSDL");
$params->a = 2;
$params->b = 3;
$objectresult = $client->Minus($params); // 注意函数名称是 "Minus"
$simpleresult = $objectresult->MinusResult; // 注意结果名称引用为 "MinusResult"
print($simpleresult); // 输出 "5"
?>
ub at sturmundbraem dot ch
8 年前
为了避免SOAP客户端有时返回对象,有时返回对象数组,可以使用以下设置

$this->soapClient = new \SoapClient($wsdlUrl, array(
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'trace' => DEBUG_SOAP
));
paulsjv at gmail dot com
18年前
我第一次使用SOAP,我必须创建一个客户端,将日期范围发送到WSDL(Web服务描述语言)以返回我需要的一些信息。我不知道如何传递参数,并且实际上没有任何关于它的文档。你必须确保的一件事是,当你将参数传递给由你调用的WSDL定义的方法时,你使用与数组键或对象变量相同的参数名称,如下所示。此外,如果你不知道WSDL拥有的方法/函数或需要传递的参数,你可以在声明新的SoapClient后使用__getFunctions()和__getTypes()方法。

<?php
// From 和 to 是 execute 函数需要调用的两个参数
// 当从 WSDL 调用时,请确保将它们作为
// 数组键,如下所示
$params["From"] = "06/01/2005"; // 也可以使用 $params->From = "date";
$params["to"] = "12/31/2005"; // 也可以使用 $params->to = "date";

$client = new SoapClient("some.wsdl");

try {
print(
$client->execute($params));
} catch (
SoapFault $exception) {
echo
$exception;
}
?>
OrionI
19年前
当通过SOAP调用.NET应用程序时,你最终可能会得到一个对象作为结果,而不是一个简单类型,即使你只是获取一个简单类型(如布尔结果)。使用属性访问器来获取实际结果,如下所示
<?php
$client
= new SoapClient("http://server/myservice.asmx?WSDL");
$objectresult = $client->MyMethod($param1, $param2);
$simpleresult = $objectresult->MyMethodResult;
?>
请注意,对于方法MethodName,.NET似乎将结果命名为MethodNameResult。
snuufix+nospam at gmail dot com
13年前
我正在使用SOAP调用响应头来签署请求结果。

经过大量的尝试,我最终找到了获取SOAP响应头的最佳方法(除了解析__getLastResponse(),这需要启用跟踪选项),那就是使用__soapCall()包装器。

你可以扩展SoapClient类并包装一些函数以确保你获取标头。

<?php

class API extends SoapClient
{

// ... 构造函数等

// 获取SOAP服务器响应头
public function __soapCall($function, $arguments, $options = array(), $input_headers = null, &$output_headers = null)
{
parent::__soapCall($function, $arguments, $options, $input_headers, $output_headers);

print_r($output_headers); // 响应头数组
}

// 如果你使用WSDL,你需要这个,这样你仍然可以直接调用函数,而无需手动调用__soapCall
public function __call($func, $args)
{
return
$this->__soapCall($func, $args);
}

?>
OrionI
19年前
关于之前提交的代码片段的更正……为了正确转换为.NET期望的XML格式,.NET的传入参数也必须采用对象或数组形式(正如Lluís Pàmies已经提到的那样)。使用WSDL时的完整示例如下所示
<?php
$client
= new SoapClient("http://server/myservice.asmx?WSDL");
$params->param1 = $value1;
$params->param2 = $value2;
$objectresult = $client->MyMethod($params);
$simpleresult = $objectresult->MyMethodResult;
?>
所以,如果你有一个像这样的C#函数
//sumservice.asmx
...
[WebMethod]
public int Sum(int a, int b)
{
return a + b;
}
...
PHP客户端将是这样的
<?php
$client
= new SoapClient("http://server/sumservice.asmx?WSDL");
$params->a = 2;
$params->b = 3;
$objectresult = $client->Sum($params);
$simpleresult = $objectresult->SumResult;
print(
$simpleresult); // 输出 "5"
?>
Tim Williams
15年前
传递参数时的一个小问题,你需要属性和简单类型值

要获取xml

<foo bar="moo">cheese</foo>

你需要传入

<?php
array("foo" => array("_" => "cheese", "bar"=>"moo"));
?>

看到那个"_"了吗?文档中并没有明确说明。
james dot ellis at gmail dot com
14年前
如果你使用这种方法,请记住,参数数组的传递顺序必须与SOAP端点期望的顺序相同。

例如
<?php
// 服务器期望:Foo(string name, int age)

// 不起作用
$args = array(32, 'john');
$out = $client->__soapCall('Foo', $args);

// 可行
$args = array('john', 32);
$out = $client->__soapCall('Foo', $args);
?>
stefan at datax dot biz
17年前
对我的__soapCall调用也返回了一个对象。这是使我的生活更轻松的函数

function obj2array($obj) {
$out = array();
foreach ($obj as $key => $val) {
switch(true) {
case is_object($val)
$out[$key] = obj2array($val);
break;
case is_array($val)
$out[$key] = obj2array($val);
break;
default
$out[$key] = $val;
}
}
return $out;
}

用法
...
$response = $client ->__soapCall("track", array('parameters' => $request));
$response = obj2array($response);

希望它能有所帮助。
deWarlock
15年前
最烦人的事情是,尝试传递与wsdl不匹配的对象时不会收到任何警告,例如,如果服务器期望类似$Object->expName->...的内容,而你传递了$Object->otherName,客户端将发送空请求而不会通知你。
还要注意名称区分大小写。

在我的情况下,我花了几个小时试图传递$Post->Lead->...对象而不是$Post->lead->...
Lluís Pàmies
19年前
如果你的服务是.NET doc/lit,这意味着输入消息有一个名为'parameters'的单个部分,它是一个包装参数的结构。你的调用应该如下所示

<?php

$params
= array('param_name_1'=>$val_1,'param_name_2'=>$val_2);
$client->call('MethodName', array('parameters' => $params));

?>
ryan at grunt dot tv
19年前
如果你想将xml文档节点作为函数参数传递,你需要创建一个SoapVar对象,其中包含xml节点的文本表示和XSD_ANYXML编码常量。但是,这个常量没有被扩展导出,并且出于某种未知的原因没有被记录。

因此,要使其工作,你必须将XSD_ANYXML #define注册为PHP常量,或者在创建SoapVar时使用常量的整数值,即147。

$soapvar = new SoapVar($xml_text, 147);

$params = array("ItemXml" => $soapvar, "PropertyView" => "blah");
$result = $this->soapclient->__soapCall("SaveItem", array("parameters"=>$params), null, $this->soapheaders);

但是,这仍然不能给出正确的结果。出于某种原因,ItemXml参数节点没有围绕soap请求中的关联xml参数进行包装,并且会产生以下soap(假设使用'<item>blah</item>'作为$xml_text)

<SOAP-ENV:Envelope xmlns:SOAP-ENV="..." xmlns:ns1="...">
<SOAP-ENV:Header>...</SOAP-ENV:Header>
<SOAP-ENV:Body>
<ns1:SaveItem>
<item>blah</item>
<ns1:PropertyView>blah</ns1:PropertyView>
</ns1:SaveItem>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Alejandro Cavallo
16年前
我遇到了ryan上面报告的相同问题
"ryan at grunt dot tv
2005年9月22日 01:38
如果你想将xml文档节点作为函数参数传递,你需要创建一个SoapVar对象,其中包含xml节点的文本表示和XSD_ANYXML编码常量。但是,这个常量没有被扩展导出,并且出于某种未知的原因没有被记录。

定义变量soapVar时,我将其类型赋值为XSD_STRING而不是XSD_ANYXML。
当调用函数时,你必须在一个关联数组中传递soapVar。

例如,如果函数(称为myFunc)期望一个名为xmlInput的参数,你应该这样做
$soapvar = new SoapVar($query, XSD_STRING);
$result = $client->myFunc(array('xmlInput'=>$soapvar));

此XML片段将围绕xmlImput标记进行包装。
pmeth at softersoftware dot com
10年前
注意,当尝试重载__soapCall函数时,不要使用类型提示,否则STRICT_STANDARDS会报错。
正确的签名如下所示

<?php
class MySoapClient extends \SoapClient
{
...
public function
__soapCall($function_name, $arguments, $options = null, $input_headers = null, &$output_headers = null)
{
...
}
....
}
?>
niko dot hujanen at gmail dot com
14年前
我正在将本地仓库系统与SOAP集成到我们的系统中,并注意到一些事情。

1) 服务使用“点命名空间”(例如Item.Price),我将其类映射到Item_Price,并使用神奇的__get()和__set()从变量名中去除“Item.”。效果很好。

例子
<Item.Price>1.52</Item.Price>

<?php
class Item_Price {
public
$price;
public function
__get($field) { $field = str_replace('Item.', '', $field); return $this->$field; }
public function
__set($field, $value) { $field = str_replace('Item.', '', $field); $this->$field = $value; }
?>

2) 我注意到可以使用类映射带有属性和值的片段。类使用变量作为属性,而 `$_` 是值。

例子
<?php
class Foo {
public
$item = 'Bar';
public
$_ = 'Value here';
}
?>
转换为 <Foo item="Bar">Value here</Foo>

使用类进行soap调用示例
<?php
$client
= ExtendSoapClient::getInstance();
$foo = new Foo();
$response = $client->sendFoo($foo);
?>

发布这些内容是因为我认为文档缺乏且非常过时。希望可以减轻一些人的负担。
lesley at casbah dot com dot au
15年前
如果您正在使用证书调用 Weblogic 服务,并且在 soap 方法调用语句中收到此错误

[EJB:010160]安全违规:用户:'<anonymous>' 权限不足,无法访问 EJB:type=<ejb>,application=app2_0_52_3,module=appsejb.jar,ejb=AppMethod,method=AppMethod,methodInterface=Remote,signature={java.lang.String}。

那么检查 soap 打开调用中的 wsdl - 它必须是 https。使用 http wsdl 提供,这花了一些时间才弄清楚。

<?php
$client
= new SoapClient("https://example.com/AppWs/Service?WSDL",
array(
'trace'=>true,
'exceptions'=>true,
'local_cert' => "/var/www/html/app/newcert.pem",
'passphrase' => "thepassphrase"));
$response = $client->__soapCall("MyMethod", array("param1" => $value1));
?>

感谢作者关于通过取消 php.ini 中 extension=php_opensll.dll 和 extension=php_curl.dll 的注释来启用 openssl 和 curl 的评论。也感谢 Olaf 的说明“将私钥文件和证书文件的内容追加到单个文件中”。我使用 DOS 提示符上的 copy 命令执行此操作

copy mycert.pem+mykey.pem newcert.pem
To Top