请注意,如果您需要设置 SOAP 请求的超时时间,可以使用 ini_set 更改 default_socket_timeout 的值。我之前使用的是 NuSOAP,其 soap 客户端类具有超时选项,我花了一段时间才弄清楚 PHP 的 soap 使用与其他所有内容相同的套接字选项。
请注意,如果您需要设置 SOAP 请求的超时时间,可以使用 ini_set 更改 default_socket_timeout 的值。我之前使用的是 NuSOAP,其 soap 客户端类具有超时选项,我花了一段时间才弄清楚 PHP 的 soap 使用与其他所有内容相同的套接字选项。
要使用 SoapServer()、WSDL 文件和 Zend Studio 客户端/服务器调试 SOAP 服务,您必须将 ?start_debug=1&debug_port=10000 附加到服务位置
--- 省略 ---
... 方法/服务定义 ....
<service name="SOAPService">
<port
name="SOAPServicePort"
binding="typens:SOAPServiceBinding">
<soap:address
location="$URL?start_debug=1&debug_port=10000"/>
</port>
</service>
--- 省略 ---
使用 WSDL 模式下的 PHP SoapServer 通过 SOAP 传递复杂类型时遇到问题?解码不正确?这可能是您正在寻找的解决方案!
在 WSDL 文件的 schema 部分使用 ComplexType 时,您需要采取额外步骤来告诉 PHP SOAP 如何编码对象。第一种方法是将对象明确封装在 SoapVar 对象中 - 告诉 PHP 使用通用的 SOAP 编码规则(将所有 ComplexType 编码为结构)。但是,如果客户端期望根据 WSDL 的模式对对象进行编码,则此方法无效。因此,实际的方法是
* 首先,定义一个特定的 PHP 类,它实际上只是一个数据结构,包含各种属性以及 WSDL 中相应的 ComplexType。
<?php
class MyComplexDataType {
public $myProperty1;
public $myProperty2;
}
?>
<complexType name="MyWSDLStructure">
<sequence>
<element name="MyProperty1" type="xsd:integer"/>
<element name="MyProperty2" type="xsd:string"/>
</sequence>
</complexType>
* 接下来,在初始化 SoapServer 时告诉它将这两个结构映射在一起。
<?php
$classmap = array('MyWSDLStructure' => 'MyComplexDataType');
$server = new SoapServer("http://MyServer/MyService.wsdl", array('classmap' => $classmap))
?>
* 最后,让您的方法直接返回类的实例,并让 SoapServer 处理编码!
<?php
public function MySoapCall() {
$o = new MyComplexDataType();
$o->myProperty1 = 1;
$o->myProperty2 = "MyString";
return $o
}
?>
处理 wsdl 和 flash soap 与 php 时
(不完全是 php,而是正确的 wsdl - 我花了几个小时才弄清楚它在 flash webservice 中正常工作)
命名定义时,
不要使用“tns:”而使用“typens:”
确保您的“definitions/targetNamespace”
与您的“soap:body”命名空间相同
示例:“urn:mynamespace”
确保您的“binding/type”使用“typens”声明
确保您的 service/port/binding 设置为 'typens:...'
如果您没有正确操作,最终会得到
WSDL.UnrecognizedNamespace - 在 flash 中
但在 php soapclient 中似乎没问题……
编码愉快 :)
希望这能帮助您节省在 wsdl 上的时间 :)
如果您不想手动维护 classmap,请确保为您的 PHP 对象类和 WSDL complexTypes 使用相同的名称,并使用以下代码
$classmap = array();
$tmpClient = new SoapClient("soapserver.wsdl");
foreach($tmpClient->__getTypes() as $type)
{
$array = split(" ", $type);
if($array[0] == "struct" && class_exists($array[1]))
{
$classmap[$array[1]] = $array[1];
}
}
unset($tmpClient);
$server = new SoapServer("soapserver.wsdl", array("classmap" => $classmap));
unset($classmap);
$server->setClass("someclass");
$server->handle();
我希望这能节省一些人的时间。在开发和测试您的 SOAP 服务器时,请记住在客户端和服务器中都禁用 WSDL 缓存
$ini = ini_set("soap.wsdl_cache_enabled", 0);
如果您正在
- 本地主机上
- 在 WSDL 模式下
尝试使用 soap 客户端连接 soap 服务器时遇到以下错误
致命错误:未捕获的 SoapFault 异常:[HTTP] 无法连接到主机……
然后在您的 WSDL 文件中将“localhost”更改为“127.0.0.1”
<soap:address location='http://127.0.0.1/soap-server.php'/>
wokan at cox dot net 对通过 URI 传递到 HTTPS URI 的值的安全性说法是不正确的。HTTPS 连接是 SSL 内部的 HTTP —— 所有 HTTP 流量,包括请求,都已加密。
如果您正在苦思冥想为什么 NuSOAP 在 PHP 5.x 上不起作用,原因是此内置 SOAP 扩展使用与 Nusoap 相同的 soapclient() 类名。
将 nusoap.php 中的“soapclient”替换为“soapclient_xxx”,您就可以开始了……
遇到这样的错误消息时
[faultstring] => 此服务的无效方法 ("yourMethod") 函数
虽然WSDL等文件中存在,但请注意PHP为了提高性能会将wsdl缓存在本地。您可以通过php.ini/.htaccess完全禁用缓存,或者删除缓存文件(如果您使用的是Linux,则为/tmp/wsdl-..),以强制重新生成它。
对于任何使用PHP Soap + Sessions + PEAR DB类的人员的提示。
每次通过soap客户端调用您的Web服务时,您的PEAR DB会话都会被挂起,并且默认情况下不会在下一个请求中唤醒。
为了解决这个问题,我只是在我的$soap->handle()之后调用了我特定的数据库关闭调用ifx_close()。
对于在Windows配置下使用SOAP证书的人员的说明:似乎需要提供证书文件的完整路径 - 并且不要添加前缀'file://'。
<?php
$wsdl = "test.wsdl";
$local_cert = "c:\htdocs\mycert.pem";
$passphrase = "xyz";
$client = new SoapClient($wsdl, array('local_cert' => $local_cert, 'passphrase' => $passphrase);
?>
哇,实际上是一个很酷的程序,而且soap对我来说是新的。
我发现了一些我无法调试的东西,因为脚本在没有任何错误消息或提示的情况下就退出了。 :-(
您可能会遇到内存问题,尤其是在服务器负载很高的情况下,在“共享服务器”上尤其如此。
有时脚本可以完成工作,有时它会在任何未知点停止。
这些是我的脚本执行的步骤
* 从远程服务器获取数据(约4.5 MB)
* 解析请求的对象并将数据存储到数据库中。
带有调试消息的返回值很有趣
-> 检查内存限制:30M
-> $client = new new SoapClient($url_wsdl, $options);
-> 内存使用量:185888
-> $client->[requested_method_to_get_data]();
-> 检查:__getLastResponseHeaders() -之后
-> HTTP/1.1 200 OK // 远程服务器对我来说没问题 :-)
-> Content-Length: 4586742 // 我得到了数据
-> 检查:现在的内存使用量:23098872 // 哎呀!!!这不可能是真的!!
所以,如果现在服务器上的某人占用剩余的RAM,则遍历数据就会中断 :-(
所以,我需要存储XML树($client->client->__last_response)并通过经典方式解析它。(如果您请求更多RAM,如果您更频繁地运行这样的脚本,则可能会遇到管理员的麻烦!(在共享服务器上)
这可能会有所帮助,因为我花了相当长的时间才发现这一点
如果您使用的是一些.wsdl,并且存在可以多次出现的序列(即:maxOccurs > 1),如果您有多个项目,则可以为其指定一个非关联数组;或者,如果只有一个项目,则可以只指定该项目。
<?php
'items' => array(
array(
'itemId' => 5,
'name' => 'some name',
),
array(
'itemId' => 6,
'name' => 'some other name',
),
),
?>
这也适用
<?php
'items' => array(
'itemId' => 5,
'name' => 'some name',
),
?>
这里有73个测试用例,详细说明了PHP5目前支持的模式类型以及您应该插入其中的结构,作为服务的返回值。比猜测好得多!
http://cvs.php.net/co.php/pecl/soap/tests/schema
您可以通过更改下面URL中的主索引来循环浏览列表,而无需进出页面
http://cvs.php.net/co.php/pecl/soap/tests/schema/schema052.phpt?r=1.2
我用这个下载了全部内容,CVS也可能有效。
http://www.httrack.com/
下载完成后,我用Textpad浏览它们。
想知道为什么您刚刚添加到WSDL文件中的函数无法用于您的SOAP客户端?关闭WSDL缓存,(如文档中所述)默认情况下它是开启的。
在脚本顶部使用
$ini = ini_set("soap.wsdl_cache_enabled","0");
在PHP5下使用SOAP扩展传输包含对象或对象数组的对象的问题。嵌套对象无法传输。
解决方案
这个类是我通过反复试验开发的。因此,对于大多数使用PHP5进行编程的开发人员来说,这23行代码解决了使用SOAP扩展的命运。
<?php
/*
根据PHP5中SOAP类的组织过程的具体情况,我们必须将复杂对象包装在SoapVar类中。否则,对象将无法正确编码,并且无法在远程SOAP处理程序上加载。
函数“getAsSoap”用于对要传输的对象进行编码。编码后,可以正确传输。
*/
abstract class SOAPable {
public function getAsSOAP() {
foreach($this as $key=>&$value) {
$this->prepareSOAPrecursive($this->$key);
}
return $this;
}
private function prepareSOAPrecursive(&$element) {
if(is_array($element)) {
foreach($element as $key=>&$val) {
$this->prepareSOAPrecursive($val);
}
$element=new SoapVar($element,SOAP_ENC_ARRAY);
}elseif(is_object($element)) {
if($element instanceof SOAPable) {
$element->getAsSOAP();
}
$element=new SoapVar($element,SOAP_ENC_OBJECT);
}
}
}
// ------------------------------------------
// 抽象示例
// ------------------------------------------
class PersonList extends SOAPable {
protected $ArrayOfPerson; // 变量必须是受保护的或公共的!
}
class Person extends SOAPable {
// 任何数据
}
$client=new SoapClient("test.wsdl", array( 'soap_version'=>SOAP_1_2, 'trace'=>1, 'classmap' => array('Person' => "Person", 'PersonList' => "PersonList") ));
$PersonList=new PersonList;
// 一些操作
$PersonList->getAsSOAP();
$client->someMethod($PersonList);
?>
因此,每个将通过SOAP传输的类都必须扩展自SOAPable类。
如您在上面的代码中看到的,函数prepareSOAPrecursive在父对象或数组中搜索另一个嵌套对象,如果找到它,则尝试调用函数getAsSOAP()来准备嵌套对象,然后通过SoapVar类简单地包装它。
所以在传输之前的代码中,只需调用$obj->getAsSOAP()
关于“DTD未识别...”错误的说明。检查以确保您的wsdl文件包含
<?xml version ='1.0' encoding ='UTF-8' ?>
还要确保您使用完整路径到您的服务(在wsdl、客户端和服务器中)
...
<wsdlsoap:address location='http://www.mysite.com.au/web_services/myserver.php' />
...
<?php
// SOAP服务器
$server = new SoapServer('http://www.mysite.com.au/web_services/hello.wsdl');
...
...
?>
<?php
// SOAP客户端
$client = new SoapClient('http://www.mysite.com.au/web_services/hello.wsdl');
...
...
?>
FYI,我不是SOAP专家,但我希望这对某些人有所帮助;)
与第三方服务合作时,我收到错误消息:“SOAP-ERROR: Parsing Schema: unexpected <text> in restriction”
我认为分享一下值得。它的意思是某个位置存在一些无效文本。C#似乎忽略了它,如果你使用nusoap,它也不会注意到它。但导致我问题的原因是这样的
<types>
<schema ...
<simpleType name="some type">
<restriction base="xsd:string">
<enumeration value="foo"/>;
</restriction>
</simpleType>
注意分号(;)。过滤掉它就可以了。
如果你使用wsdl,
请确保正确定义输入。
如果你的方法不包含任何输入参数,
你必须确保:
- 不要为输入创建消息标签。
- 不要在porttype/operation中放置输入。
- 不要在binding/operation中放置输入。
否则,你会收到错误
[客户端] 看起来我们没有收到XML
d***,我花了几个小时才找出原因……
对于那些使用充满了复杂类型的wsdl,只想获得一个类结构来挂载代码,并且不想担心输入冗长的参数列表(或创建脚本来执行此操作)的人来说:wsdl2php是一个极好的省时工具。它会生成一个结构,这样你就可以添加所需的验证和特殊数据处理: http://www.urdalen.no/wsdl2php/
为你自己点赞,Knut。
如果你使用带有证书和密码身份验证的SSL
$wsdl = "https://ws.ecopatz.de/ProductInfo?wsdl";
$pass = 'a password';
$certFile = "./mycert.pem";
$client = new SoapClient($wsdl,
array(
'local_cert' => $certFile,
'passphrase' => $pass
)
);
如果你遇到类似这样的证书文件问题
Warning: SoapClient::__construct(): Unable to set local cert chain file `./mycert.pem'; Check that your cafile/capath settings include details of your certificate and its issuer in productinfo.php on line 27
那么证书文件可能格式错误(可能是PHP的错误格式)。当我将私钥文件和证书文件的内容追加到单个文件“mycert.pem”中时,它对我有用。
cat mycert.key >mycert.pem # mycert.key 是私钥
cat mycert.crt >>mycert.pem # mycert.crt 是已签名的证书
感谢某位作者,他指出了“curl --cert”,其中提到了这个“非常不重要”的依赖项。
如果你想为Microsoft Office的客户端(如Microsoft Office Research Service)构建SOAP服务器,你需要重写SOAP的命名空间
<?php
// (...)
$server = new SoapServer($wsdl, array('uri' => $uri, 'classmap' => $classmap));
$server->setClass($class);
function callback($buffer)
{
$s = array('<ns1:RegistrationResponse>', 'ns1:', 'xmlns:ns1="urn:Microsoft.Search"');
$r = array('<RegistrationResponse xmlns="urn:Microsoft.Search">', '', '');
return (str_replace($s, $r, $buffer));
}
ob_start('callback');
$server->handle();
ob_end_flush();
// (...)
?>
此URL中有一个完整的示例: http://touv.ouvaton.org/article.php3?id_article=104
以下是如何传递ArrayOfAnyType参数的示例
包含复杂类型。
假设你的WSDL文件定义了 "http://any.url.com/" 作为默认命名空间和复杂类型"SomeComplexType"。
如果你想调用一个接受"SomeComplexType"的ArrayOfAnyType参数的Web服务,你需要执行以下操作
<?php
// complexTypes 是一个包含多个 SomeComplexType 实例的数组
myWSParameter = array();
foreach (complexTypes as ct)
{
// 不要拼错类型或命名空间。还要注意,php不会假设WSDL文件中定义的默认命名空间。
myWSParameter []= new SoapVar(ct, 0, "SomeComplexType", "http://any.url.com/");
}
?>
另一方面,当Web服务返回ArrayOfAnyType时,你需要执行以下操作来访问其每个元素。
<?php
// 在这里,我们将输出每个返回项
$res = $someWS->myFunction($myArgs)
// 如果只返回一个元素,则不会构建数组
if (is_array(myFunctionResult->anyType))
{
foreach (myFunctionResult->anyType as $soapVar)
{
echo $soapVar->enc_value;
}
}
else
{
echo myFunctionResult->anyType->enc_value;
}
?>
所有这些都已使用.NET Web服务进行了测试。
如果你在调用.NET web服务时遇到问题,请查看https://php.net/soap_soapclient_soapcall(__soapCall方法)上的评论。
在URI中传递用户名和密码并不是良好的安全实践,因为SSL的目的是防止这些信息被拦截。将这些信息放在URI中会使其可被拦截。HTTPS发布的值是安全的,因为在标头中传递的值是在SSL握手完成之后发送的。
我花了一段时间才通过Windows/Apache1.3上的https正确建立受密码保护的客户端连接。这是我的简要指南
1. SOAP扩展默认情况下未激活(PHP5 RC1)。只需将“extension=php_soap.dll”添加到php.ini中,并不要忘记正确设置extension_dir(在大多数情况下为“c:\php\ext”)。
2. 将“extension=php_openssl.dll”添加到php.ini中。此模块依赖于libeay32.dll和ssleay32.dll - 将它们从你的php文件夹复制到你的system32文件夹。
3. 重启apache
4. 源代码
$client = new SoapClient("https://yourLogin:[email protected]/bar.wsdl", array(
"login" => "yourLogin",
"password" => "yourPassword",
"trace" => 1,
"exceptions" => 0));
$client->yourFunction();
print "<pre>\n";
print "Request: \n".htmlspecialchars($client->__getLastRequest()) ."\n";
print "Response: \n".htmlspecialchars($client->__getLastResponse())."\n";
print "</pre>";
目前似乎需要在uri和options数组中同时添加你的登录名和密码。不确定这是否是预期的行为。
我的电脑上安装的是PHP 5.2.5。我得到以下结果
数组
(
[0] => stdClass 对象
(
[客户端] => stdClass 对象
(
[Nom] => LeNom:00000
[Prenom] => LePrenom:00000
)
)
我的主机使用的是5.1.6,相同的调用返回以下结果
数组
(
[0] => stdClass 对象
(
[客户端] => 数组
(
[0] => stdClass 对象
(
[Nom] => LeNom:00000
[Prenom] => LePrenom:00000
)
5.2.5是好的结构。
注意你使用的版本。更改所有代码可能需要大量工作
对于那些想知道如何在PHP5 SOAP中设置节点属性的人,可以这样做
<... soap env/header>
<foo bar="blah">12345</foo>
array("foo" => array("_" => 12345, "bar" => "blah"));