近期项目需要与Java对接, 为 Java方提供 SOAP服务,
我们知道SOAP服务 分为 有WSDL与没有WSDL的两种.
我们实现了较为复杂的有WSDL的这种
1.注意要点: PHP环境中 php.ini 中always_populate_raw_post_data 必须设置为 -1 (默认不是)
否则会出现 各种错误.
2.网上找了SoapDiscovery.class.php ,使用后,只有一点不太好用, 就是 location地址不合适,我对这个类进行了改造,代码下附
/**
* 生成WSDL的SOAP类,修改版
**/
class soapDiscovery
{
/**
* 构造方法检查PHP.INI设置
* soapDiscovery constructor.
*/
public function __construct()
{
if(ini_get('always_populate_raw_post_data')!=-1){
throw new Exception('php.ini setting always_populate_raw_post_data must be -1');
}
}
/**
* 生成 k="v" ... 的属性串
* @param $array array 属性键值数组
* @return string 属性串
*/
private function kvs(array $array)
{
$ret = '';
foreach ($array as $k => $v) {
$ret .= ' ' . $k . '="' . $v . '"';
}
return $ret;
}
/**
* 生成一个标签
* @param $name string 标签名称
* @param $open bool 是否开放标签(闭合标签自带/>)
* @param array $properties 属性键值数组
* @return string 标签的字符串表达
*/
private function tag($name, $open, array $properties)
{
return 'kvs($properties) . ($open ? '' : '/') . '>' . "\n";
}
/**
* 生成WSDL
* @param string $class_name 服务类名
* @param string $service_name 服务名称
* @param string $location 提供服务的地址
* @return string
* @throws
**/
public function getWSDL($class_name, $service_name, $location)
{
//对处理响应的类 反射
$class = new ReflectionClass($class_name);
if (!$class->isInstantiable()) {
throw new Exception('Class is not instantiable.');
}
//换行符
$n = "\n";
$schemas = 'http://schemas.xmlsoap.org/';
//头部空间定义
$headerWSDL = '' . $n;
$headerWSDL .= $this->tag('definitions', true, [
'name' => $service_name,
'targetNamespace' => 'urn:' . $service_name,
'xmlns:wsdl' => $schemas . 'wsdl/',
'xmlns:soap' => $schemas . 'wsdl/soap/',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:soap-ENC' => $schemas . 'soap/encoding',
'xmlns' => $schemas . 'wsdl/'
]);
$headerWSDL .= $this->tag('types', false, ['xmlns' => $schemas . 'wsdl/']);
$portTypeWSDL = $this->tag('portType', true, ['name' => $service_name . 'Port']);
$bindingWSDL = $this->tag('binding', true, [
'name' => $service_name . 'Binding',
'type' => 'tns:' . $service_name . 'Port'
]) . $this->tag('soap:binding', false, [
'style' => 'rpc',
'transport' => $schemas . 'soap/http'
]);
$serviceWSDL = $this->tag('service', true, ['name' => $service_name])
. $this->tag('documentation', false, [])
. $this->tag('port', true, [
'name' => $service_name . 'Port',
'binding' => 'tns:' . $service_name . 'Binding'
]) . $this->tag('soap:address', false, ['location' => $location])
. '' . $n
. '' . $n;
$body = $this->tag('soap:body', false, [
'use' => 'encoded',
'namespace' => 'urn:' . $service_name,
'encodingStyle' => $schemas . 'soap/encoding/'
]);
$messageWSDL = '';
$methods = $class->getMethods();
foreach ($methods as $method) {
if (!$method->isPublic() or $method->isConstructor()) continue;
$methodName = $method->getName();
$portTypeWSDL .= $this->tag('operation', true, ['name' => $methodName])
. $this->tag('input', false, ['message' => 'tns:' . $methodName . 'Request'])
. $this->tag('output', false, ['message' => 'tns:' . $methodName . 'Response'])
. '' . $n;
$bindingWSDL .= $this->tag('operation', true, ['name' => $methodName])
. $this->tag('soap:operation', false, ['soapAction' => 'urn:' . $service_name . '#' . $class_name . '#' . $methodName])
. '' . $n
. $body
. '' . $n
. '' . $n
. $body
. '' . $n
. '' . $n;
$messageWSDL .= $this->tag('message', true, ['name' => $methodName.'Request']);
$parameters = $method->getParameters();
foreach ($parameters as $parameter) {
$messageWSDL .= $this->tag('part', false, [
'name' => $parameter->getName(),
'type' => 'xsd:string'
]);
}
$messageWSDL .= '' . $n
. $this->tag('message', true, ['name' => $methodName . 'Response'])
. $this->tag('part', false, ['name' => $methodName, 'type' => 'xsd:string'])
. "" . $n;
}
$portTypeWSDL .= "".$n;
$bindingWSDL .= "".$n;
return sprintf('%s%s%s%s%s%s', $headerWSDL, $portTypeWSDL, $bindingWSDL, $serviceWSDL, $messageWSDL, '');
}
}
3. 服务端 生成WSDL的代码如下
$soap = new soapDiscovery();
header("Content-type:text/xml");
//此处需要根据SAOP服务器地址进行修改
echo $soap->getWSDL('MSupervise', 'cmdService','http://www.tgj.com/supervise/process');
这里 的参数1, 是 你用来处理SOAP请求的类的名称,请事先加载类
参数2. 是 一个 服务名称的标识,请自行定义, 只要在服务端与客户端保持一致即可
参数3. 是处理SOAP请求的地址(服务端)
生成WSDL后,显示在浏览器上, 如果需要保存成文件 ,注意: 查看源代码后再复制保存.否则 会 丢失第一行XML定义
4.客户端 调用示例如下
public function test(){
ini_set("soap.wsdl_cache_enabled",0);
ini_set('soap.wsdl_cache_ttl',0);
$wsdl='http://www.tgj.com/wsdl/supervise.wsdl?wsdl';
$soap=new SSoapClient($wsdl,[
'uri'=>'cmdService',
'trace'=>1,
"style" => SOAP_RPC,
"use" => SOAP_ENCODED
]);
try{
$ret=$soap->command();
dump($ret);
}catch(Exception $e){
dump($e);
}
} 其中 两行ini_set在开发调试时使用,上线时去除.
$wsdl是一个可以访问到 服务端保存的那个WSDL文件 的地址
uri是服务标识符,要与服务端那个标识符保持一致
5.如果程序运行不通, 调试会比较麻烦
我查找 网上资料后得到 一个方法,扩展了 SOAPClient类
/**
* 扩展SOAPClient类,以便加调试代码
* User: 蓝冰
* Date: 2017/5/25
* Time: 11:44
*/
class SSoapClient extends SoapClient
{
public function __doRequest($request, $location, $action, $version, $one_way = 0)
{
$request = parent::__doRequest($request, $location, $action, $version, $one_way);
//dump($request);exit;
return $request;
}
} 在调用时,使用这个子类, 即可在这个扩展类中加入打印等调试语句