PHP通过SoapClient调用WebService的wsdl格式接口时的注意事项

1. 前言

话说这是个比较让我长见识的问题。虽然做了这么久的技术,但调用接口一直都是用json。通过WebService调用数据还是第一次接触。
由于自己对WebService不熟悉,所以理所应当的夹起尾巴做人,再不露怯的情况下先研究了一下有关WebService的知识,然后开始照着甲方给的WebService接口文档一个一个的去测试。现在觉得已经出师了,所以把过程记录下。

2. 关于阅读wsdl

wsdl是什么?wsdl怎么撰写?这个更多的是WebService服务器端的事情,也就是说这是提供WebService的人应该考虑的事情,如果你只需要调用WebService,需要了解的内容非常少。只需要基本看懂wsdl就可以了。
wsdl本质上就是XML,其中重要的标签有两个一个是<operation>另一个是<sequence>下的<element>

  • <operation>代表WebService可以调用的方法名称。
  • <sequence>下的<element>代表调用方法时需要的参数。

3. 关于传参的问题

3.1 通过键值对数组向方法传参。

假如我们在调用WebService的时候,需要向方法传参,需要以键值对数组的形式传进去。
为啥要强调这个问题呢?
因为在我拿到的接口文档中,调用接口的方法是以Java形式给出的例子:

public String InvokeIntoBaas(String token, String docId, String DocType)

于是乎我也顺理成章的在php中用下面的写法:

<?php
$client = new \SoapClient('http://XXXXX/XXXX?wsdl');

$token = "asd87a98s4ga515sd3ga12ga897sd";
$docId = "32";
$DocType = "tscDoc";

$result = $client->InvokeIntoBaas($token, $docId, $DocType);
var_dump($result);
?>

然后你会发现,这样肯定是无法运行成功的。
向方法传值,一定是通过键值对数组,数组里面写明参数名称和参数值,也就是说无论有多少个参数,都是向方法里面传一个键值对数组就够了。
就像下面这种方式:

<?php
$client = new \SoapClient('http://XXXXX/XXXX?wsdl');

$param = [];
$param["token"] = "asd87a98s4ga515sd3ga12ga897sd";
$param["docId"] = "32";
$param["DocType"] = "tscDoc";

$result = $client->InvokeIntoBaas($param);
var_dump($result);
?>

只有通过键值对数组的方式,WebService才能把传入的内容准确的识别。也就是说,WebService识别传入的参数不是通过参数的前后顺序,而是通过参数键值对中的键名。

3.2 参数内容以wsdl中的内容为准。

为什么上面要强调下wsdl呢?是因为我拿到WebService的接口文档时,接口文档里面写的一些内容无论我怎样试验都无法使用成功。最后发现,接口文档时间太久了,其中的某个字段名称已经更新。
而我是怎么知道的呢?
是通过看wsdl中<sequence>下的那些<element>知道的。
换句话说,再你向WebService中的方法传参的时候,参数键值对中的键名一定要和wsdl中的名字一一对应,差一个字母也不可以。

必须要提的
就通过这一点,我很肯定的指出了文档的错误,一个现学现用的新手给WebService开发人员还上了一课。是不是很厉害!哈哈哈!

4. 几个重要的函数

echo("打印暴露的方法:");
var_dump($client->__getFunctions());
print("<br/>");
echo("打印对应方法的参数和参数类型:");
var_dump($client->__getTypes());
print("<br/>");
echo("最后一次请求的头数据:");
var_dump($client->__getLastRequestHeaders());
echo "<br>";
echo("最后一次请求的数据:");
var_dump($client->__getLastRequest());
echo "<br>";
echo("最后一次返回的头数据:");
var_dump($client->__getLastResponseHeaders());
echo "<br>";
echo("最后一次返回的数据:");
var_dump($client->__getLastResponse());

PHP中使用SoapClient对象访问wsdl得到空object(stdClass),而__getLastResponse()方法却能获取XML返回结果的解决方案

1. 问题重现

<?php
$client = new \SoapClient('http://192.168.0.111:8989/dbservice/selectAll?wsdl');
$param = [];
$param['users'] = 'all';
$result =  $client->selectAllinspectOperation($param);
var_dump($result);
?>

运行后的返回结果如下,以下结果是全部返回内容,并没有隐藏什么:

object(stdClass)[2]
  public 'record' => 
    array (size=2)
      0 => 
        object(stdClass)[3]
      1 => 
        object(stdClass)[4]

2. 思考过程

  • 首先,运行结果没有报错。
  • 然后,selectAllinspectOperation()这个函数的目的是返回数据库中所有的结果。而数据库中目前只有两条数据,所以从返回结果来看,正好有个长度为2的数组。所以我认为查询是成功,且返回了正确的条目数。
  • object(stdClass)这个东东我比较陌生,查了下php文档,是个叫做基类的对象。具体是啥东东对本问题不是很重要,就不赘述了。当时我认为问题就是在object(stdClass)上,可能object(stdClass)把返回结果都封装隐藏起来了,所以看不到。
  • 于是乎又是一顿狂查,网上查到了一个函数get_object_vars()可以把object转为数组,试了一下,确实是把对象转为数组了,上面代码最后一句修改以下,运行结果如下:
    //var_dump(get_object_vars($result));运行结果
    array (size=1)
    'record' => 
    array (size=2)
      0 => 
        object(stdClass)[3]
      1 => 
        object(stdClass)[4]

    不过上面的结果只把最外层的object转为了数组,于是再深入以下,把里面的object(stdClass)也转为数组,同样是最后一行代码修改下,运行结果如下:

    //var_dump(get_object_vars(get_object_vars($result)["record"][0]));运行结果
    array (size=0)
    empty
  • 这下子又晕了,得到的是空值。于是又是一顿狂查,查到了几个SoapClient中的函数,可以打印SoapClient调用过程中的各种信息,于是把每个函数都写下来看看能返回什么结果:
    echo("打印暴露的方法:");
    var_dump($client->__getFunctions());
    print("<br/>");
    echo("打印对应方法的参数和参数类型:");
    var_dump($client->__getTypes());
    print("<br/>");
    echo("最后一次请求的头数据:");
    var_dump($client->__getLastRequestHeaders());
    echo "<br>";
    echo("最后一次请求的数据:");
    var_dump($client->__getLastRequest());
    echo "<br>";
    echo("最后一次返回的头数据:");
    var_dump($client->__getLastResponseHeaders());
    echo "<br>";
    echo("最后一次返回的数据:");
    var_dump($client->__getLastResponse());

    最重要的就是最后一个__getLastResponse()函数,发现所有期望的查询结果实际都返回回来了:
    file

  • 这就奇怪了不是吗?明明已经返回了查询结果,为什么之前获取不到呢?于是我放弃了这个接口,而是测试接口文档中其它的接口,却惊喜的发现其它接口是可以正常返回数据的,虽然返回的也是object(stdClass)类型数据,但是object(stdClass)下面是有具体的数值的。于是我开始怀疑,是不是给的接口有问题?
  • 于是又是一轮查询和思考。说实话,这方面的文档真的很少。经过国内、国外各种网站的查询和信息汇总。最终确定,确实是接口有问题。

3. 问题说明。

基本可以肯定的是,如果可以通过__getLastResponse()看到数据,而打印的数据是空object(stdClass),这个不是使用WebService的问题,而是服务器端WebService的返回值不符合相关规范,导致SoapClient无法对返回结果正常解析导致的。
所以,不要再症结这个问题了,除非你能修改WebService的内容,否则常规手段解决不了这个问题。

4. 解决方案。

之前也说过了,可以通过__getLastResponse()获取返回的内容。反正已经有了结果了,直接处理结果字符串不就好了吗。
所以解决方案很简单,直接使用PHP的XML处理函数,或者直接写个正则表达式获取里面的结果就好了!

5. 总结

我把以上这些情况反馈给了接口提供方,那边也最终证实,确实是他们返回的数据格式有瑕疵。
重要的事情说三遍,遇到这种情况:
不是你的问题!
不是你的问题!
不是你的问题!
是WebService的问题!
是WebService的问题!
是WebService的问题!