我们成功地与 Weather 服务取得了联系并显示了结果但是如果出现错误得不到预期的结果该怎么办?ext/soap 可以显示客户机与服务器之间交换的 SOAP 消息能够帮助我们确定问题所在
只有使用 trace 选项创建 SoapClient 时才要使用跟蹤功能我们在 options 数组参数中设置 trace 选项将该参数传递给 SoapClient 构造函数我们将构造函数的使用改为
$soapClient = new SoapClient(//localhost:/
ItsoWebServiceRouterWeb/wsdl/itso/session/WeatherForecastEJBwsdl
array(trace => ));
并在调用 goForecast 之后调用 trace 方法
echo Request :<br> htmlspecialchars($soapClient>__getLastRequest()) <br>;
echo Response :<br> htmlspecialchars($soapClient>__getLastResponse()) <br>;
一定要使用 htmlspecialchars 内置函数对 trace 输出进行编码因为它将 SOAP xml 分界符转换成特殊字符如 <这样可以避免浏览器将其解释成标记
下面是某个请求的 trace 输出
<?xml version= encoding=UTF?>
<SOAPENV:Envelope xmlns:SOAPENV=
xmlns:ns=>
<SOAPENV:Body>
<ns:getForecast>
<ns:startDate>T::</ns:startDate>
<ns:days></ns:days>
</ns:getForecast>
</SOAPENV:Body>
</SOAPENV:Envelope>
对应的应答是
<?xml version= encoding=UTF?>
<soapenv:Envelope xmlns:soapenv=
xmlns:soapenc=
xmlns:xsd=
xmlns:xsi=instance>
<soapenv:Body>
<getForecastResponse xmlns=>
<getForecastReturn xmlns:ns=>
<ns:condition>sunny</ns:condition>
<ns:date>T::Z</ns:date>
<ns:windDirection>W</ns:windDirection>
<ns:windSpeed></ns:windSpeed>
<ns:temperatureCelsius></ns:temperatureCelsius>
<ns:dbflag></ns:dbflag>
</getForecastReturn>
</getForecastResponse>
</soapenv:Body>
</soapenv:Envelope>
如果在开启跟蹤功能的情况下运行客户机来收集这些输出那么需要将 days 参数设置为 只有这样做SOAP 应答才会输出较少的行但是我们遇到了没有预料到的行为我们本来期望 getForecastResponse 和以前一样是一个 Weather 对象数组但是它应该只有一个元素而不是 个元素然而它被转换成了一个简单的 Weather 对象我们必须根据这种行为进行编码就像您在最终的示例 PHP 客户机代码中看到的那样这与 Java 客户机的行为有所不同在客户机行为中getForecast 总是返回 Weather 对象数组无论服务器响应中有多少个 Weather 对象SoapClient::_getTypes() 输出并没有为我们理解这种差异提供足够的细节因此我们要求助于 WSDL 文档来了解完整的接口规范
解释 WSDL
我们已经成功地调用了 Weather 服务但是还没有看过它的 WSDL 文档WSDL 中的细节要比 SoapClient 公开的多我们如何知道应该在 startDate 参数中放什么呢?我们应该期望从返回的数据中实际得到什么?要回答这些问题必须更深入地分析 WSDL
可以从下载部分下载 Weather Forecast 应用程序的 WSDL如果使用不同的 Web 服务只需要在浏览器中打开相应的 WSDL 文档即可
getForecast 操作的 WSDL 是
<wsdl:operation name=getForecast>
<wsdl:input message=intf:getForecastRequest name=getForecastRequest/>
<wsdl:output message=intf:getForecastResponse name=getForecastResponse/>
</wsdl:operation>
其中的 getForecastRequest 消息被定义为
<wsdl:message name=getForecastRequest>
<wsdl:part element=intf:getForecast name=parameters/>
</wsdl:message>
而 getForecast 结构被定义为
<element name=getForecast>
<complexType>
<sequence>
<element name=startDate nillable=true type=xsd:dateTime/>
<element name=days type=xsd:int/>
</sequence>
</complexType>
</element>
于是我们知道该函数需要两个参数xsd:dateTime 类型的 startDate 和整数类型的 days这与我们所了解的 SoapClient::_getTypes 函数完全匹配但是现在我们还知道 startDate 可以为空(nillable)毫无疑问如果我们简化输入参数那么该函数将如下所示
$forecastResponse = $soapClient>getForecast(array(startDate=>Null days=>));
如果明确指定今天的日期结果会与所指定的完全一致
如果希望制定其他起始日期怎么办呢?XML Schema将 dateTime 定义成一种基本类型按照 ISO 标准格式化比如T::假设希望了解三天之后的天气预报可以使用内置函数 strtotime(+ days) 获得需要的日期该函数与 time() 函数相同都返回标准 UNIX 格式的日期时间即表示从公元纪年开始到现在的秒数的一个整数我们知道 XML Schema 要求日期采用具有字符串字段的 ISO 格式进行编码于是在示例客户机中编写了 timeToIso 函数将整数日期转换成 SOAP 编码定义的格式但我们吃惊地发现其实并不需要这样做ext/soap 非常聪明地将整数日期转化成了需要的字符串字段格式无论传递的是整数还是预格式化的字符串都没有关系最终传送的 SOAP 消息都是一样的
响应中的日期又如何呢?在回程中ext/soap 从 SOAP 响应获得了 dateTime 字段但是没有做任何格式转换我们希望它返回一个整数以表示从公元纪年到现在的秒数但实际上得到的是按照 ISO 格式化的字符串于是我们使用 strtotime 函数将其转化成整数然后使用 strftime 格式化该整数以便于表示
Weather Service 按日期提供预报但它忽略了 dateTime 编码中的时间成分所以我们没有考虑这方面的调整如果从运行在不同时区内的服务中请求天气预报那么可能必须这样做如果希望进一步了解时区转换请参阅参考资料中给出的描述 ISO 标准的文章
现在再回到响应格式上来上一节中曾经提到 getForecast 返回数据的不一致性WSDL 描述告诉我们 getForecast 返回一个 getForecastResponse 对象getForecastResponse 可以包含无限多个称为 Weather 的复杂类型的列表
<element name=getForecastResponse>
<complexType>
<sequence>
<element maxOccurs=unbounded name=getForecastReturn type=tns:Weather/>
</sequence>
</complexType>
</element>
<complexType name=Weather>
<sequence>
<element name=condition nillable=true type=xsd:string/>
<element name=date nillable=true type=xsd:dateTime/>
<element name=windDirection nillable=true type=xsd:string/>
<element name=windSpeed type=xsd:int/>
<element name=temperatureCelsius type=xsd:int/>
<element name=dbflag type=xsd:boolean/>
</sequence>
</complexType>
WSDL 不允许出现单元素数组这种特例不幸的是当响应只包含一个 Weather 对象时ext/soap 没有考虑 WSDL 中应用于 getForecastResponse 的 <sequence> 标签因为这种行为在客户机代码中造成了不必要的复杂性
最后WSDL 文档还告诉 SOAP 客户机可以从网络中的哪个地方找到该服务
<wsdl:service name=WeatherForecastEJBService>
<wsdl:port binding=intf:WeatherForecastEJBSoapBinding
name=WeatherForecastEJB>
<wsdlsoap:address location=
//localhost:/ItsoWebServiceRouterWeb/services/WeatherForecastEJB/>
</wsdl:port>
</wsdl:service>
处理 SOAP 错误
如果运行客户机时出现错误怎么办?与其他语言(如 Java)一样PHP 新增加了一种异常机制ext/soap 使用这种新的机制以 SoapFault 对象的形式返回错误比方说可以用下面这种形式将代码包装起来
try {
some SOAP operation
} catch (SoapFault $soapFault) {
echo $soapFault;
}
注意与 Java 有所不同PHP 语言的 try catch 块不能包含 finally