分类: PHP

老婆家的故事

上次说了外公的故事,这次说说我老婆家的故事,确切的说是我老婆爷爷奶奶的故事,我老婆的爷爷家是地主,他们家现在那个村庄的地在解放前都是他们家的,现在还有些邻居是以前给他们家打长工的,不过我老婆的爷爷算是开明地主,后来还加入了共产党,不过也就是这个身份,当时差点丢了性命,据说被国民党抓了,本来都要枪毙了,子弹是个臭子儿,侥幸逃脱,具体啥情况我也没了解清楚,不过确实很惊险。老婆的奶奶是大家闺秀,有钱人家的小姐,陪嫁中有一张大床,据说当时就值几十个大洋,后来日本人来了,老婆爷爷奶奶的家产全被掠夺光了,据说当时拉了好几卡车,所以我对老婆说,和日本人是家仇国恨,要是没有小日本,你奶奶说不定还能给你留些好东西呢。老婆的爷爷在几年前已经去世了,奶奶健在,身体很好,我说奶奶活到100岁肯定没问题,他们都是历史的见证者,真心希望老婆的奶奶身体健康,长命百岁。

—分割线—

今天在写Objective-C时候,解析接口返回的Json数据时出现了类型转换错误,具体就是字符串转整型数字出现错误,我仔细看了一下,是PHP接口返回的Json数据中数字型的数据,被加上了双引号,解决方法是在json_encode后面的参数设置为JSON_NUMERIC_CHECK,比如json_encode($jsonmsg, JSON_NUMERIC_CHECK);

修改之后运行出现错误,我查了一下,原来JSON_NUMERIC_CHECK这个参数是在PHP5.3.3加上的,而我用的Mac版的XAMPP集成环境中,PHP只到5.3.1,看来要升级,后来发现Mac版的XAMPP没有新版了,只能使用另一个集成环境MAMP,它提供了Mac环境下的Apache、Mysql、PHP的集成环境,我已经安装的MAMP里PHP版本为5.3.14,应该没问题,我直接修改MAMP目录下Apache的配置文件,增加虚拟目录(Alias),但修改之后重启MAMP就是不生效,非常奇怪,我搜了半天,也没发现是什么问题,后来发现在MAMP的菜单里,File->Edit Template->Apache->httpd.conf,编辑httpd.conf,之后重启MAMP终于好了。

—分割线—

欢迎订阅我的微信公共帐号:“Laoer杂谈”,搜索微信号:laoertalk,或扫描页面右侧的二维码
新浪微博:@laoer
Twitter:@laoer

支付宝无线商户接入遇到的问题总结

最近我在为“星点无线”产品增加手机支付功能,支付当然首先想到的是支付宝,而且也在其他类似无线产品里看到集成支付宝应用。

首先在支付宝无线商户平台里签约,我们分别签了“手机网页支付”和“手机应用内支付”,所在行业的费率会有所不同,请根据实际情况选择,“手机网页支付”和“手机应用内支付”的费率也不同,“手机应用内支付”低一些。

最开始,我只想简单一点,在iOS应用里内嵌Web,采用手机网页支付,服务器端是用PHP,手机网页支付,采用MD5和RSA两种签名方式,MD5相对简单,但安全性不如RSA,我最开始测试MD5,而且之前在网站上集成支付宝,也没啥问题,开始是比较顺利的,到支付宝支付时,支付宝默认选择了短信支付方式(可能和我们签约的分类有关),也就是支付宝会通过手机短信发送一个验证码,然后回复验证码,支付宝再会发送一个短信,短信里包含一个URL,点击这个URL,会提示支付完成,然后转到我们网站的回调地址,我方程序完整成个订单,但支付宝提供的这个URL,有时候打开会报错误,而且也不是每次都错,非常的奇怪,更大的问题是在成功后回调我服务器程序时报签名不对,我仔细检查了配置,没有问题,只能一点点排错,最后发现支付宝提供的Demo包里alipay_function.php的para_filter方法有问题,这个方法会漏掉回传的out_trade_no参数,非常的奇怪,只有修改这个方法为

function para_filter($parameter) {
  $para = array();
  foreach ($parameter as $key=>$val) {
    if($key == "sign" || $key == "sign_type" || $val == "") {
      continue;
    } else {
      $para[$key] = $parameter[$key];
    }
  }
  return $para;
}

签名问题解决,但由于短信支付最后这个URL的诡异表现,我为此专门询问支付宝客户支持,得到的回答是他们也发现这个问题,但不知道是什么原因造成的,技术也解决不了,竟然也有支付宝解决不了的问题啊!

这个问题不能解决,确实对用户的体验不太好,我只能选择“手机应用内支付”,支付宝提供了iOS的集成代码,文档也算详尽,但iOS的签名采用RSA,所以在服务器端先要改为RSA的(RSA的alipay_function.php para_filter方法有同样的问题),生成公钥、私钥的方法在文档里比较清楚,就是注意上传商户公钥时一定要删除文件头“—–BEGIN PUBLIC KEY—–”与文件尾“—–END PUBLIC KEY—–”还有空格、换行,变成一行字符串并保存为TXT文件,商户私钥在PHP程序不需要转为PKCS8格式,而在其他语言里(比如Objective-C)需要转为PKCS8格式,并在iOS应用里设置plist文件中RSA private key变量时,要把PKCS8格式私钥的换行和头尾的“—–BEGIN PRIVATE KEY—–”与“—–END PRIVATE KEY—–”删除。

在iPhone真机调试过程中,跳到支付宝应用时报签名错误,我又仔细检查了好几遍,确定没什么问题,后来在支付宝的指导下先测试运行他们提供的Demo程序,没有问题,说明签名密钥没问题,肯定是要签名的数据有问题,我对比了一下发现AlixPayOrder中的productDescription不能为空,也就是传输数据中的body变量不能为空,终于完成了无线支付的集成。

支付宝提供的文档还算比较全面,但有些地方还是有些含糊,如果是iOS集成的话,先测试它提供的Demo程序,以确定密钥的正确性,仔细调试,记录Log,还是比较容易集成的。

Nginx下alias支持PHP的问题

这几天在配置Nginx,PHP用FastCGI,想装一个phpMyAdmin管理数据库,phpMyAdmin不想放在网站根目录下,这样不容易和网站应用混在一起,这样phpMyAdmin的目录就放在别处,在Apache里,有alias,比较方便,在Nginx下没有虚拟目录概念的,是用location配合alias使用,我先试了简单的配置方式

location /web/ {
alias  /data/web/;
index  index.html index.htm index.php;
}

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

我用http://localhost/web/可以访问到/data/web目录下的静态文件,但访问php文件,却报No input file specified.的错误,而且在Nginx的error日志上却什么信息也没有,我在网上搜索了一下,判断应该是php文件并没有被后端的FastCGI运行,我又继续搜索一些文章,试着增加了一段配置

location /web/ {
alias  /data/web/;
index  index.html index.htm index.php;
}

location ~ ^/web/.+\.php$ {
root /data/;
rewrite /web/(.*\.php?) /$1 break;
include fcgi.conf;
fastcgi_pass   127.0.0.1:9000;
fastcgi_index  index.php;
fastcgi_param SCRIPT_FILENAME /data/web$fastcgi_script_name;
}

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

这下可以了,原理应该是采用rewrite的方法,对于/web/下php类型的的请求交给后端的FastCGI处理,并且指定了php脚本的位置,这样我们就可以配置phpMyAdmin了,配置如下

location /phpmyadmin/ {
alias  /data/phpmyadmin/;
index  index.html index.htm index.php;
}

location ~ ^/phpmyadmin/.+\.php$ {
root /data/;
rewrite /phpmyadmin/(.*\.php?) /$1 break;
include fcgi.conf;
fastcgi_pass   127.0.0.1:9000;
fastcgi_index  index.php;
fastcgi_param SCRIPT_FILENAME /data/phpmyadmin$fastcgi_script_name;
}

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

要注意的是

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

这段,要放在phpmyadmin的后面,放在前面就有问题,这是和Nginx的location规则有关,具体看Nginx的文档,另外,phpMyAdmin里要配置一下URI的绝对路径,就可以了。

WordPress升级到2.8遇到的问题

上周末的时候看到后台提示可以升级到wordpress2.8,我就简单做了一个文章导出,之后在线升级到2.8,我之前的版本是2.7.1,提示升级成功,但转到“控制板”之后,提示错误

Allowed memory size of  xxxxxxxxx bytes exhausted

2.8难道有问题,我可没有做完整的备份,2.8的数据库有改动,如果2.8真的有问题就麻烦了,这个错误是说PHP分配内存太小,只要修改php.ini就可以,但我现在用的是虚拟主机,不能修改php.ini的,还是网上搜一下吧,很快,找到了答案,只要在wp-settings.php里修改为

define(‘WP_MEMORY_LIMIT’, ’64M’);

就可以了(以前是32M,如果不行可以继续改大)。

Memory解决了,但后台还是提示有新版本2.8,要我升级,我不是已经升级成功了吗,奇怪,后来查了一下,由于我用的是中文版,所以在wp-includes/version.php里增加一行

$wp_local_package = ‘zh_CN’;

就不会再提示有新版本了。

PHP中的时间时区运算

我在测试twitter的API时,在它返回的用户信息里,关于用户时区的信息有两个

<utc_offset>28800</utc_offset>
<time_zone>Beijing</time_zone>

我希望利用这两个数值,得到用户的的DateTimeZone,直接new DateTimeZone(‘Beijing’)是不行的,我看了PHP的API,时区里是没有“Beijing”的,中国的时区应该是“Asia/Shanghai”,看来这个“Beijing”只是用来显示的,我又查了DateTimeZone的API,里面也没有根据UTC Offset值取得DateTimeZone的方法,看来不能通过这样的方法来做了。

utc_offset这个值是应该表示与UTC标准时间相差的秒数,测试下面的代码

$datetimezone = new DateTimeZone(‘Asia/Shanghai’);
$datetime = new DateTime(“now”,$datetimezone);
echo $datetime->getOffset();

结果显示28800,没错,时区“Asia/Shanghai”与UTC的时间差是28800秒,也就是8个小时(我们在东8区)。

在PHP中使用time()或是DateTime对象,在不指定DateTimeZone时,其默认的DateTimeZone就是UTC,产生的时间就是UTC时间,把这个时间加上Offset值之后,就可以显示出本时区的正确时间了,比如下面的代码

$datetime = time();
echo date(“y-m-d H:i:s”,$datetime);
echo “<br/>”;
$datetime = $datetime + 28800;
echo date(“y-m-d H:i:s”,$datetime);

输入的结果是
09-05-13 04:21:44
09-05-13 12:21:44

上面是UTC的时间,而下面的是北京时间。

在我们设计数据的时候,时间字段设计为长整数字段比较方便,存放UTC的time()的Unix时间戳,用户可以自己定义所在的时区,通过Offset,最终显示给用户其时区的时间。我感觉Java里的TimeZone似乎要更灵活一些,可以这样TimeZone.getTimeZone(“GMT+08:00”)

另外:在PHP里取得所有可用时区的方法是DateTimeZone::listIdentifiers()。

Sphinx+Mysql初使用体验

应用越来越多的需要全文搜索技术来支撑,在Java中可以使用Lucene,一个非常优秀的引擎,在Hibernate中也整合了Lucene来做检索,但在使用PHP的过程中迫切需要找一个优秀的全文搜索引擎(虽然也可以把PHP和Lucene结合起来使用,但有些另类,有些生产环境也不能同时支持),以前在网上看到一些Sphinx的文章,一直没有实践,昨天我就花了一天的时间,配置测试了一下Sphinx。

由于我的PHP没有编译Sphinx模块,所以我主要是配置Sphinx+Mysql,在Mysql上测试全文搜索的效果,Mysql、Sphinx、中文分词的编译安装过程不详述,下面两篇文章挺好,安装时可以参考

Mysql+sphinx+中文分词简介

基于Sphinx+MySQL的千万级数据全文检索(搜索引擎)架构设计

我的编译安装过程没有遇到什么麻烦,就是编译Mysql的时候比较长,而且要注意把Innodb的引擎编译进去,我的Mysql编译参数如下

./configure –with-plugins=sphinx,innobase,heap –prefix=/usr/local/mysql –enable-assembler –with-charset=utf8 –with-extra-charsets=all –enable-thread-safe-client –with-big-tables –with-readline –with-ssl –with-embedded-server –enable-local-infile

比较奇怪的是innodb是支持了,heap不支持,这个问题下次再研究。

通过编译Mysql,使Mysql支持了Sphinx存储引擎,试了一下Sphinx的例子,成功搜索到了数据,如果我们要对自己的数据做索引,就要研究一下Sphinx的配置文件了。

Sphinx的配置文件在其安装目录下的etc目录下,你可以参考其例子的sphinx.conf创建自己的配置文件,在sphinx里有主要要配置的有两大块,一部分是source(数据源),另一部分是index(索引),source里面定义了连接数据库的参数,取得源数据的SQL,也就是你要索引的数据的取得 SQL(Sphinx是支持不同数据源的,我这里只测试SQL),source可以有继承关系,继承的source可以用来做取得增量数据,index里面定义了使用哪个source,index存放的路径、字符集、辞典等等,index也可以继承,继承的index用来做增量索引。由此可见source 和index都是根据你的需要配置的,可以取得多个数据源的数据,可以建立多个索引。

增量索引的小困惑,我在数据库中增加了2条记录,执行

/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1stemmed

看到有两个文档被加入索引(增量部分),之后我执行

/usr/local/sphinx/bin/indexer –rotate –merge test1 test1stemmed –merge-dst-range deleted 0 0

将增量索引并入主索引,这是可以查询到新插入的数据,这时我再继续插入一条数据,执行

/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1stemmed

提示信息是有3个文档被加入索引,让我非常奇怪,因为上两条纪录已经并入主索引了,这次怎么还会索引呢?之后我执行

/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1

更新主索引,之后再执行/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1stemmed提示没有索引加入,这样就正确了,如果按照测试的结果,增量索引和主索引更新执行的时间要计划好。

关于中文分词–LibMMSeg:LibMMSeg 是Coreseek.com为 Sphinx 全文搜索引擎设计的中文分词软件包,其在GPL协议下发行的中文分词法,采用Chih-Hao Tsai的MMSEG算法。

同时Coreseek.com提供了一份Sphinx的中文文档,里面有比较详细的配置说明,是很好的参考资料,非常感谢开源人士做出的贡献。

Short Url(短网址)实现方式

现在提供Short Url(短网址)的网站越来越多了,特别是Twitter的助Short Url的一臂之力,越来越多的人开始使用Short Url-短网址,现在比较流行的几个Short Url网站有

TinyURL
http://tinyurl.com/

Bit.Ly
http://bit.ly/

Is.Gd
http://is.gd/

等等,Short Url的作用在于把长的Url缩成短的Url,比如,我前两天些的一篇博客,Url是https://i.laoer.com/think-about-http-get-chinese-encode-error.html,我们采用TinyURL,转成的Url是http://tinyurl.com/d4zw8x,只有25个字符,短了很多,请求http://tinyurl.com/d4zw8x的时候,tinyurl会把请求通过HTTP 301转到https://i.laoer.com/think-about-http-get-chinese-encode-error.html上。

实现Short Url的功能并不复杂,但最关键的就是这个短代码要够短,而且需要唯一,我们的例子是“d4zw8x”,6位,还有就是用户输入的同一个Url,应该返回唯一的Short Url,用户在请求长的Url之后,先从数据库查找一下这个长Url是否存在,如果存在,就直接取出其对应的短代码,如果不存在,则生成短代码,与用户的长Url同时保存在数据库中。

最核心的这个短代码的实现方式,我在网上找了两个

第一个是纯随机数的算法,来自Short URL Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function random($length, $pool = '')
    {
        $random = '';
 
        if (empty($pool)) {
            $pool    = 'abcdefghkmnpqrstuvwxyz';
            $pool   .= '23456789';
        }
 
        srand ((double)microtime()*1000000);
 
        for($i = 0; $i < $length; $i++) 
        {
            $random .= substr($pool,(rand()%(strlen ($pool))), 1);
        }
 
        return $random;
    }

另一个算法来自http://www.snippetit.com/2009/04/php-short-url-algorithm-implementation/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function shorturl($input) {
  $base32 = array (
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
    'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
    'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
    'y', 'z', '0', '1', '2', '3', '4', '5'
    );
 
  $hex = md5($input);
  $hexLen = strlen($hex);
  $subHexLen = $hexLen / 8;
  $output = array();
 
  for ($i = 0; $i < $subHexLen; $i++) {
    $subHex = substr ($hex, $i * 8, 8);
    $int = 0x3FFFFFFF & (1 * ('0x'.$subHex));
    $out = '';
 
    for ($j = 0; $j < 6; $j++) {
      $val = 0x0000001F & $int;
      $out .= $base32[$val];
      $int = $int >> 5;
    }
 
    $output[] = $out;
  }
 
  return $output;
}

其返回的是一个4个元素的数组,应为存在可能的重复性,你可以依次使用这4个元素。

Short Url的算法应该还有一些,Short Url网站的作用除了缩短网址以外,在使用者不断增加之后,可以积累庞大的网址信息,这对统计分析是很有用的。

Kohana的Cache

Kohana里有个Cache Library,我今天就测试了一下。

首先要配置,将system/config下的cache.php复制到application/config下,打开cache.php文件,我们看一下内容

1
2
3
4
5
6
7
$config['default'] = array
(
    'driver'   => 'file',
    'params'   => APPPATH.'cache',
    'lifetime' => 1800,
    'requests' => 1000
);

这是一个默认配置,’driver’为驱动的缓存方式,Kohana支持6种不同的驱动,分别是File、SQlite、Memcache、APC、Eaccelerator、Xcache,配置文件默认使用的file,其原理是,写缓存时把对象序列化写入文件,读缓存时从文件读出文本反序列化,所以在文件方式下,缓存是基于I/O的,在文件多而且大的时候,性能会有下降, ‘params’是驱动参数,在file驱动模式下,就是cache文件路径,’lifetime’是cache的生命周期,单位为秒,超过这个时间,内容将被清除(设置为0代表不自动清除),’requests’为在达到请求数量之前自动垃圾回收。

在应用中,可能需要不止一个缓存,所以可以配置多个缓存,增加$config数组即可,还有在file方式下可以为每个缓存设置单独文件路径,但前提是文件路径要存在,例如我们增加一个

1
2
3
4
5
6
7
$config['my'] = array
(
    'driver'   => 'file',
    'params'   => APPPATH.'cache/my',
    'lifetime' => 1800,
    'requests' => 1000
);

接下来我们在Controller里调用cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Mycache_Controller extends Controller {
 
    public function index() {
        $mychache = Cache::instance("my");
        $mychache->set("name","laoer");
        echo "OK";   
    }
 
    public function name() {
        $mychache= Cache::instance("my");
        $name = $mychache->get("name");
        echo $name;
    }   
}

Cache::instance(“my”)实例化配置里$config[‘my’]的cache,如果用Cache::instance()就是实例化配置里$config[‘default’]的cache,在浏览器里执行,已经可以存取了,在application/cache/my/文件下可以看到一个名为”name~~0″的文件,里面就是序列化的数据。

文件cache还是有一定的局限性,现在越来越的网站开始使用Memcached所谓缓存的解决方案,Kohana的缓存驱动里,有Memcached的支持,但它的文档却没有给出Memcached的配置例子,看来要自己摸索一下。

将system/config下的cache_memcache.php复制到application/config下,cache_memcache.php的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @package  Cache:Memcache
 *
 * memcache server configuration.
 */
$config['servers'] = array
(
	array
	(
		'host' => '127.0.0.1',
		'port' => 11211,
		'persistent' => FALSE,
	)
);
 
/**
 * Enable cache data compression.
 */
$config['compression'] = FALSE;

根据你自己的情况修改Memcached的服务地址和端口,在application/config/cache.php里再加一段

1
2
3
4
5
6
7
$config['mem'] = array
(
	'driver'   => 'memcache',
	'params'   => '',
	'lifetime' => 1800,
	'requests' => 1000
);

在Controller里把Cache::instance(“my”)改为Cache::instance(“mem”),运行看看结果,已经可以从Memcahced里存取了。

Kohana的Memcached驱动还是有些缺陷,现在只能使用一组Memcached,即$config[‘servers’]这个参数,我觉得Memcacahed组也应该是多个,因为从业务角度会根据功能对cache做划分,我大概看了一下system/libraries/drivers/Cache/Memcache.php文件,应该是可以改造的,还有一点,编译PHP的时候要安装Memcached的支持。

PHP SOAP实践

我现在公司里的系统都是SOA/面向服务架构的,用Java编写的Web Service,理论上Web Service支持异构语言的,我想测试一下用PHP来调用Java的Web Service是否方便。

首先用Java建立一个Web Service服务端(用CXF,JAX-WS实现),下面是公布出来的WSDL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version='1.0' encoding='UTF-8'?><wsdl:definitions name="SampleWebService" targetNamespace="http://www.abc.net/WS/2008" xmlns:ns1="http://webservice.passport.abc.net/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.abc.net/WS/2008" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:import location="http://192.168.19.42:8080/sample/service/SampleWebService?wsdl=SampleWebService.wsdl" namespace="http://webservice.passport.abc.net/">
    </wsdl:import>
  <wsdl:message name="helloUser">
    <wsdl:part element="ns1:helloUser" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="helloUserResponse">
    <wsdl:part element="ns1:helloUserResponse" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:binding name="SampleWebServiceSoapBinding" type="ns1:SampleWebService">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="helloUser">
      <soap:operation soapAction="" style="document" />
      <wsdl:input name="helloUser">
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output name="helloUserResponse">
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="SampleWebService">
    <wsdl:port binding="tns:SampleWebServiceSoapBinding" name="SampleWebServicePort">
      <soap:address location="http://192.168.19.42:8080/sample/service/SampleWebService" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

这个Web Service有个“helloUser”方法,传入一个参数,它返回一段文本“Hello 参数!”,

PHP5之后已经加入了SOAP的支持,我最开始用PHP5的SOAP函数来调用,下面是代码

1
2
$client = new SoapClient('http://192.168.19.42:8080/sample/service/SampleWebService?wsdl');
var_dump($client->__getFunctions());

但报错Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: <message> ‘helloUser’ already defined in …,我搜了PHP网站上的相关文档也没找到答案,没办法,我只能找其他SOAP的组件,找到了NuSOAP,它最近发布的版本是0.7.3,是在2007年发布的,现在已经2009了,用用试试吧,参考nusoap的例子,写好自己代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require_once('nusoap.php');
$client = new nusoap_client('http://192.168.19.42:8080/sample/service/SampleWebService?wsdl', 'wsdl');
$err = $client->getError();
if ($err) {
	echo '<h2>Constructor error</h2><p>' . $err . '</p>';
}
$param = array('arg0'=>'laoer');
$result = $client->call('helloUser', array('parameters' => $param),"http://webservice.passport.abc.net/",'', false, false,"document","literal");
 
if ($client->fault) {
	echo '<h2>Fault</h2><p>';
	print_r($result);
	echo '</p>';
} else {
	// Check for errors
	$err = $client->getError();
	if ($err) {
		// Display the error
		echo '<h2>Error</h2><p>' . $err . '</p>';
	} else {
		// Display the result
		echo '<h2>Result</h2><p>';
		//print_r($result);
		echo $result["return"];
		echo '</p>';
	}
}
 
echo '<h2>Request</h2><p>' . htmlspecialchars($client->request, ENT_QUOTES) . '</p>';
echo '<h2>Response</h2><p>' . htmlspecialchars($client->response, ENT_QUOTES) . '</p>';

执行结果

Result
Hello laoer!

Request
POST /sample/service/SampleWebService HTTP/1.0
Host: 192.168.19.42:8080
User-Agent: NuSOAP/0.7.3 (1.114)
Content-Type: text/xml; charset=UTF-8
SOAPAction: “”
Content-Length: 436

我们可以看到Request的SOAP文本

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://webservice.passport.abc.net/">
	<SOAP-ENV:Body>
		<helloUser xmlns="">
			<arg0 xmlns="">laoer</arg0>
		</helloUser>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Response
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
SOAPAction: “”
Content-Type: text/xml;charset=UTF-8
Content-Length: 237
Date: Fri, 27 Mar 2009 04:27:11 GMT
Connection: close

Response的SOAP文本

1
2
3
4
5
6
7
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ns2:helloUserResponse xmlns:ns2="http://webservice.passport.abc.net/">
			<return>Hello laoer!</return>
		</ns2:helloUserResponse>
	</soap:Body>
</soap:Envelope>

碰到一个小问题就是PHP给方法传参数,看是我不知道怎么填,后来我用soapUI,做了一个SOAP Request的例子,发现参数的Name是arg0,如果是多个参数,则arg0、arg1、arg2…以此类推,所以我们定义的参数是$param = array(‘arg0’=>’laoer’),还有就是nusoap里对返回信息的编码有点问题,如果你Web Service放回的编码是UTF-8,则nusoap多转换了一次,在nusoap.php文件nusoap_client类里把$decode_utf8置为false就可以了。