soapclient 不直接读取 xml 文件,仅通过 wsdl 或非 wsdl 模式生成/发送 soap 请求;复用已有 xml 需重写 __dorequest() 或改用 curl 直接发送。

PHP 的 SoapClient 不直接读 XML 文件
你手头有个 xxx.xml,想“用它调用 SOAP 接口”——这本身是个误解。SoapClient 不解析或加载外部 XML 请求体;它根据 WSDL(或手动配置)生成符合 SOAP 协议的请求,底层自动序列化参数、拼装信封。所谓“用 XML 调用”,实际只有两种可行路径:一是让 SoapClient 生成请求后拦截/替换为你的 XML,二是绕过 SoapClient 直接发 HTTP POST。
常见错误现象:Fatal error: Uncaught SoapFault exception 或返回 HTTP 500,但服务端日志显示“无效的 SOAP Envelope”,往往是因为手动拼的 XML 缺少命名空间、SOAP-ENV:Envelope 结构错位,或编码不匹配。
- WSDL 模式下,
SoapClient会严格校验参数类型和结构,传入任意 XML 字符串会直接报SOAP-ERROR: Encoding: object has no 'xxx' property - 非 WSDL 模式(即构造时传
NULL作第一个参数)可跳过 WSDL 校验,但必须手动指定location和uri,否则请求发不到正确地址 - XML 中的命名空间前缀(如
ns1:)必须与服务端期望完全一致,大小写敏感,漏一个冒号就失败
用 __doRequest() 拦截并注入自定义 XML
这是最贴近“用已有 XML 调用”的方案:继承 SoapClient,重写 __doRequest(),在发送前把默认生成的 XML 替换为你自己的。
使用场景:你已调试好一份能被服务端接受的 XML(比如用 SoapUI 测试通过),现在想在 PHP 中复用它,同时保留 SoapClient 的连接管理、SSL 处理等能力。
立即学习“PHP免费学习笔记(深入)”;
关键点在于:重写方法必须返回服务端响应的原始 XML 字符串,不能做任何 DOM 解析或修改,否则 SoapClient 内部解析会失败。
本支付接口的特点,主要是用xml文件来记录订单详情和支付详情。代码比较简单,只要将里面的商户号、商户key换成你自己的,将回调url换成你的网站,就可以使用了。通过这个实例也可以很好的了解一般在线支付接口的基本工作原理。其中的pay.config文件记录的是支付详情,order.config是订单详情
- 构造子类时,
__soapCall()的第一个参数(方法名)可以是任意字符串(如'dummy'),因为真实请求体已被替换 - 你的 XML 必须包含完整的
SOAP-ENV:Envelope,且根节点的xmlns:SOAP-ENV声明要和服务端 WSDL 一致(常为http://schemas.xmlsoap.org/soap/envelope/或http://www.w3.org/2003/05/soap-envelope) - 如果服务端要求
Content-Type: text/xml; charset=utf-8,需在__doRequest()中显式设置,PHP 默认可能发application/soap+xml
class CustomSoapClient extends SoapClient
{
private $customXml;
public function __construct($wsdl, $options, $xml)
{
parent::__construct($wsdl, $options);
$this->customXml = $xml;
}
public function __doRequest($request, $location, $action, $version, $oneWay = 0)
{
$headers = [
"POST {$location} HTTP/1.1",
"Content-Type: text/xml; charset=utf-8",
"SOAPAction: {$action}",
"Content-Length: " . strlen($this->customXml),
];
$response = $this->sendHttpRequest(implode("\r\n", $headers) . "\r\n\r\n" . $this->customXml, $location);
return $response;
}
private function sendHttpRequest($data, $url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:']);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
不用 SoapClient,直接 cURL 发送 XML
如果你的 XML 已验证有效,且不需要 WSDL 解析、类型映射、故障重试等高级功能,直接 cURL 更轻量、更可控。
性能影响:省去 SoapClient 初始化开销(尤其是 WSDL 解析),但失去内置的 SOAP 错误码映射(如把 SOAP-FAULT 自动转成 SoapFault 异常)。
容易踩的坑集中在 HTTP 头和编码:
-
SOAPAction头不是可选的——某些老服务(尤其 .NET ASMX)强制校验,值必须和 WSDL 中soap:operation的soapAction属性一致,空字符串或缺失都会拒收 - XML 字符串必须是 UTF-8 编码,且声明中写
<?xml version="1.0" encoding="UTF-8"?>;如果源 XML 是 GBK,mb_convert_encoding($xml, 'UTF-8', 'GBK')必须做 - cURL 默认启用 Expect: 100-continue,部分 SOAP 服务不支持,需加
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Expect:'])清空
WSDL 地址返回 404 或解析失败怎么办
很多企业 SOAP 接口的 WSDL 是内网地址、带鉴权,或动态生成(如 ?wsdl 后缀被防火墙拦截)。此时硬依赖 SoapClient($wsdl) 必然失败。
解决思路不是“怎么修 WSDL”,而是绕过它:
- 用浏览器或
curl -v http://example.com/service?wsdl确认能否拿到完整 XML;拿不到就别走 WSDL 模式 - 非 WSDL 模式下,
location必须是真实的 SOAP 端点 URL(通常是http://...,不是?wsdl地址),uri是目标命名空间(看 WSDL 的targetNamespace,或抓包看请求里xmlns:tns="...") - 如果连 WSDL 都看不到,唯一办法是联系对接方索要接口文档,或用 Wireshark 抓其他客户端发出的真实请求,反推命名空间和动作名
真正麻烦的永远不是语法,而是服务端对命名空间、SOAP 版本(1.1 vs 1.2)、HTTP 头的隐性要求——这些几乎从不写在文档里,只能靠抓包比对。










