月度归档:2014年08月

PHP函数参考 – 其它基本扩展 – Misc.(杂项函数)

这个Misc(杂项函数)比较有意思,提供了一些很奇特的方法。不过这些方法还经常被用到。所以这里简单罗列一下。

connection_aborted  — 检查客户端是否已经断开
connection_status   — 返回连接的状态位
connection_timeout  — 检查脚本是否已超时

ignore_user_abort   — 设置客户端断开连接时是否中断脚本的执行

PHP脚本维护一个链接状态,一般在0 – NORMAL(正常)下运行,发生异常时处于1 – ABORTED(异常退出),超时时处于2 – TIMEOUT(超时),ABORTED(异常退出)一般是用户终止请求导致的,可以通过设置配置选项ignore_user_about或调用ignore_user_abort()函数来忽略用户的终止;当连接时间超过PHP的时限(可以通过配置选项或设置set_time_limit函数)时,TIMEOUT 状态的标记将被打开。

constant            — 返回一个常量的值
define              — 定义一个常量
defined             — 检查某个名称的常量是否存在

使用define定义一个常量的操作非常常见,而defined则是检测常量是否已经定义。constant返回常量的值,看起来这个函数多余,但是在处理用变量存储常量名时,通过constant这个变量,可以得到常量的值,这个对于动态生成常量已经动态获取常量值时,还是非常有应用价值的。

die                 — 等同于 exit
exit                — 输出一个消息并且退出当前脚本

这两个杂项函数也很常见,直接终止脚本执行。

eval                — 把字符串作为PHP代码执行

PHP提供的这个函数可以让字符串当做代码来执行,对于运行动态生成的代码,看起来还比较不错。

highlight_file      — 语法高亮一个文件
highlight_string    — 字符串的语法高亮
php_check_syntax    — 检查PHP的语法(并执行)指定的文件
php_strip_whitespace — 返回删除注释和空格后的PHP源码
show_source         — 别名 highlight_file

这几个函数处理PHP源代码,php_check_syntax函数看起来还有一定的应用价值,特别是在使用eval()执行代码时,先检测一下语法,或者可以先检测没有语法错误再执行脚本。

pack                — Pack data into binary string
unpack              — Unpack data from binary string

这两个奇葩函数直接忽略了。

sleep              — 延缓执行
time_nanosleep     — 延缓执行若干秒和纳秒
time_sleep_until   — 使脚本睡眠到指定的时间为止。
usleep             — 以指定的微秒数延迟执行

这几个函数跟冻结脚本执行有关,sleep以秒单位的,usleep以微秒为单位(1秒=10的6次方微秒)。

get_browser        — 获取浏览器具有的功能
__halt_compiler    — 中断编译器的执行
sys_getloadavg     — 获取系统的负载(load average)
uniqid             — 生成一个唯一ID

这几个是真正的杂项函数。uniqid函数生成的唯一ID是跟微秒有关的,至于如何保证唯一就让我有点好奇了。sys_getloadavg还能获取系统负载,看不出有什么作用,真要做负载均衡也不会依靠PHP来干这个事。

以上重点说明的几个函数还是能记住会比较好。

永久链接:http://blog.ifeeline.com/1176.html

PHP函数参考 – 其它基本扩展 – JSON

从PHP 5.2开始,JSON模块模板编译进PHP,可以使用–disable-json明确禁止JSON模块。

JSON标准参考:http://www.json.org/

函数列表:

    json_decode — 对 JSON 格式的字符串进行编码
    json_encode — 对变量进行 JSON 编码
    json_last_error_msg — Returns the error string of the last json_encode() or json_decode() call
    json_last_error — 返回最后发生的错误

这几个函数使用都非常简单,通过json_last_error_msg()函数可以获取最后出现的错误。

PHP引入这个扩展后使得数据交换更加容易和方便,比如和JS的交互,比如API的调用返回JSON数据,它比返回XML更加直观。

永久链接:http://blog.ifeeline.com/1174.html

PHP函数参考 – 国际化与字符编码支持 – iconv

PHP中的iconv模块是libiconv函数的包装器,一般POSIX兼容系统提供的C语言标准库应该都支持iconv,否则需要在系统上安装libiconv。默认此模块是被激活的,如果要禁用可以添加–without-iconv编译PHP。选项指令–with-iconv-dir用于PHP编译时指定 iconv在系统里的路径,否则会扫描默认路径。

这个扩展跟mbstring有类似之处,可以先参考PHP函数参考 – 国际化与字符编码支持 – 多字节字符串

此扩展提供了三个运行时配置,跟mbstring是类似的:

iconv.input_encoding	"ISO-8859-1"
iconv.output_encoding	"ISO-8859-1"	
iconv.internal_encoding	"ISO-8859-1"

内部编码用于转换编码时的默认值。

这个模块重点掌握iconv()函数。其它的字符串操作函数iconv_strlen()、iconv_strpos()、iconv_strrpos()、iconv_substr()在mbstring中也提供了对应的函数。

string iconv ( string $in_charset , string $out_charset , string $str )

将字符串 str 从 in_charset 转换编码到 out_charset。对于out_charset,如果在 out_charset后添加了字符串//TRANSLIT,将启用转写(transliteration)功能。这个意思是,当一个字符不能被目标字符集所表示时,它可以通过一个或多个形似的字符来近似表达。 如果添加了字符串//IGNORE,不能以目标字符集表达的字符将被默默丢弃。 否则,str从第一个无效字符开始截断并导致一个E_NOTICE。

可以发现,这个函数比mb_convert_encoding()函数更加人性化,一般在涉及字符编码转换时使用iconv()函数比较好。

永久链接:http://blog.ifeeline.com/1172.html

浏览器提交数据编码测试

PHP脚本文件使用GB2312编码
Chrome浏览器能自动检测到是GBK编码,文件中的中文字符(GB2312编码)能正确显示。
1)POST提交中文字符数据,编码是EUC-CN(GB2312),GET提供中文字符串数据(?s=字符编码),检测到提交过来的数据是UTF-8编码,显示也是乱码(因为使用GBK编码解释UTF-8编码的数据)。
2)AJAX POST提交中文字符数据,编码是UTF-8;AJAX GET提供中文字符串数据,编码是EUC-CN(GB2312)

火狐浏览器测试除了AJAX GET提供中文字符串数据,编码是UTF-8外(尽管JS脚本是使用GB2312编码)其它跟chrome一致。

Safari for Windows测试跟chrome一致。

Opera测试跟chrome一致。

IE 8(win 7捆绑),在GET提供中文字符串数据(?s=字符编码),检测到提交过来的数据是EUC-CN(GB2312),这个跟firefox和chrome和Opera都不一样。其它跟chrome一致。

360安全浏览器(基于IE内核),跟chrome一样。

说明:AJAX的POST提交使用JQuery的$.post方法,在查看请求头时,发现:

Content-Type	application/x-www-form-urlencoded; charset=UTF-8

这个字符编码不确定是浏览器添加的还是JQuery添加的,如果是JQuery添加的,那么它还要负责把POST的数据转换成UTF-8编码,所以估计应该是浏览器行为。

从以上比较可以看出,POST提交的数据的编码跟form表单的编码一致,但是GET提交的数据在有中文时一般采用了UTF-8编码,但是IE浏览器视乎不遵守这个规则,高版本的IE估计会遵守规则,不过这个奇葩还是尽量少用或不用。

使用Ajax方法提交数据时,在GET提交数据上,视乎是跟脚本的编码一致的,但是firefox直接无视这个通用规则,它使用UTF-8编码。在POST提交数据上,都一致使用UTF-8编码(没有确认是否是JQuery的行为还是浏览器行为)。

当把脚本改为utf-8编码时,不管是什么方式,提交的数据带中文时都是utf-8编码的,唯独IE例外,IE中(Win 7自带版本),GET和AJAX GET提交的数据都是EUC-CN(GB2312)编码的,360浏览器(IE内核),AJAX GET也是EUC-CN(GB2312)。

最后结论是,珍惜生命,请远离IE。GET提交中文数据,服务端得到数据编码可能不一致,尽管大多数浏览器都使用UTF-8编码(低版本浏览器可能不是)。POST提交的数据时跟form表单编码一致,所以是可控的。AJAX提交数据也有差异性,所以在处理多字节字符时,最好统一使用utf-8编码。

最后附上测试程序:
GB2312编码
UTF-8编码

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1168.html

PHP函数参考 – 国际化与字符编码支持 – 多字节字符串

PHP函数参考 – 国际化与字符编码支持 – 多字节字符串

PHP的多字节字符串对应mbstring扩展来实现,默认并没有启用,需要在编译时带上enable-mbstring激活。libmbfl对mbstring 是必要的。libmbfl被捆绑到了mbstring。如果系统已安装libmbfl,–with-libmbfl[=DIR]可以指定使用已安装的库(不需要安装,已经被捆绑)。

如果编译时添加了–disable-mbregex,将禁用正则表达式函数中多字节字符的支持(默认支持)。

一般,在编译时只要添加enable-mbstring激活mbstring扩展即可。

在进入mbstring模块的探索之前,必须先了解一些编码的信息,可以参考:
关于字符编码的基本知识
Windows中的UTF-8与Unicode实验

另外,关于PHP源码文件和字符串编码之间的关系也是需要理解的,PHP中的字符串,不管字符串是什么编码,永远都是字节流,记住这个很重要。那么字符串采用什么编码呢,很简单,使用跟源文件一样的编码,比如源文件是utf-8,那么源文件里面的字符串的编码也是utf-8,但是对待这个字符串时被看做是字节流,所以对于多字节字符,PHP中的原生函数将产生问题,比如strlen函数,总是返回字符串的字节个数,对于采用多字节编码的字符串,就要采用mb_strlen函数。

模块mbstring提供了字符串操作函数的对应函数:

strlen()	mb_strlen()
strpos()	mb_strpos()
strrpos()	mb_strrpos()
strrpos()	mb_strrpos()
strtolower()	mb_strtolower()
strtoupper()	mb_strtoupper()
stripos()	mb_stripos()
strripos()	mb_strripos()
strstr()	mb_strstr()
stristr()	mb_stristr()
strrchr()	mb_strrchr()
substr_count()	mb_substr_count()

模块mbstring提供了一个配置选项用来重载这些函数:

mbstring.func_overload		"2"

要使用函数重载功能,设置php.ini里的mbstring.func_overload为正值,就是表示为重载函数分类的位掩码组合。要重载mail()函数需要设置它为1。字符串函数设置为2,正则表达式函数为4。例如,当它设置为7, mail、strings和正则表达式函数将都会被重载。

注意:这个所谓的重载,就是当启用时,如果源代码中调用了strlen()函数,那么它实际被调用的是mb_strlen()函数,实际操作过程中,无法保证稳定性,所以一般明确采用mb_xxx函数。

模块mbstring提供的运行时配置:

mbstring.language		"neutral"
mbstring.detect_order		NULL
mbstring.http_input		"pass"	定义 HTTP 输入字符的默认编码
mbstring.http_output		"pass"	定义 HTTP 输出字符的默认编码
mbstring.internal_encoding	NULL	定义内部字符的默认编码
mbstring.script_encoding	NULL	
mbstring.substitute_character	NULL	
mbstring.func_overload		"0"	
mbstring.encoding_translation	"0"
mbstring.strict_detection	"0"

mbstring.language设置直接影响到mbstring.internal_encoding和mbstring.detect_order设置。有效的语有:”Japanese”,”ja”,”English”,”en” 和 “uni”(UTF-8)。

通过如下程序检测:

<?php
echo "mb_get_info()------------>\n";
print_r(mb_get_info());

echo "mb_language()------------>\n";
echo mb_language()."\n";

echo "mb_internal_encoding()--->\n";
echo mb_internal_encoding()."\n";

echo "mb_detect_order()-------->\n";
print_r(mb_detect_order());

当mbstring.language为”neutral”时(默认)输出:

/usr/local/php-5.5.15/bin/php mb.php
mb_get_info()------------>
Array
(
    [internal_encoding] => ISO-8859-1
    [http_output] => pass
    [http_output_conv_mimetypes] => ^(text/|application/xhtml\+xml)
    [func_overload] => 0
    [func_overload_list] => no overload
    [mail_charset] => UTF-8
    [mail_header_encoding] => BASE64
    [mail_body_encoding] => BASE64
    [illegal_chars] => 0
    [encoding_translation] => Off
    [language] => neutral
    [detect_order] => Array
        (
            [0] => ASCII
            [1] => UTF-8
        )

    [substitute_character] => 63
    [strict_detection] => Off
)
mb_language()------------>
neutral
mb_internal_encoding()--->
ISO-8859-1
mb_detect_order()-------->
Array
(
    [0] => ASCII
    [1] => UTF-8
)

当mbstring.language为”en”时输出:

/usr/local/php-5.5.15/bin/php mb.php
mb_get_info()------------>
Array
(
    [internal_encoding] => ISO-8859-1
    [http_output] => pass
    [http_output_conv_mimetypes] => ^(text/|application/xhtml\+xml)
    [func_overload] => 0
    [func_overload_list] => no overload
    [mail_charset] => ISO-8859-1
    [mail_header_encoding] => Quoted-Printable
    [mail_body_encoding] => 8bit
    [illegal_chars] => 0
    [encoding_translation] => Off
    [language] => English
    [detect_order] => Array
        (
            [0] => ASCII
            [1] => UTF-8
        )

    [substitute_character] => 63
    [strict_detection] => Off
)
mb_language()------------>
English
mb_internal_encoding()--->
ISO-8859-1
mb_detect_order()-------->
Array
(
    [0] => ASCII
    [1] => UTF-8
)

当mbstring.language为”uni”时输出:

/usr/local/php-5.5.15/bin/php mb.php
mb_get_info()------------>
Array
(
    [internal_encoding] => UTF-8
    [http_output] => pass
    [http_output_conv_mimetypes] => ^(text/|application/xhtml\+xml)
    [func_overload] => 0
    [func_overload_list] => no overload
    [mail_charset] => UTF-8
    [mail_header_encoding] => BASE64
    [mail_body_encoding] => BASE64
    [illegal_chars] => 0
    [encoding_translation] => Off
    [language] => uni
    [detect_order] => Array
        (
            [0] => ASCII
            [1] => UTF-8
        )

    [substitute_character] => 63
    [strict_detection] => Off
)
mb_language()------------>
uni
mb_internal_encoding()--->
UTF-8
mb_detect_order()-------->
Array
(
    [0] => ASCII
    [1] => UTF-8
)

注意看mb_detect_order()的输出,默认并没有去检测更多的编码(mb_list_encodings函数可以输出所有支持的编码),所以如果使用mb_detect_encoding()来检测字符串编码时,第二参数没有指定检测的编码顺序,这个函数就使用默认的detect_order,可以使用mb_detect_order()函数来设置字符的检测顺序:

$encodings = array(
	//ascii
	'ASCII',
		
	//unicodes
	'UTF-8',
	'UTF-16',
		
	//chinese
	'EUC-CN',  //gb2312
	'CP936',   //gbk
	'EUC-TW',  //big5
		
	//japanese
	'EUC-JP',
	'SJIS',
	'eucJP-win',
	'SJIS-win',
	'JIS',
	'ISO-2022-JP'
);
$charset = mb_detect_encoding($str, $encodings);
// or ...
mb_detect_order($encodings);
$charset = mb_detect_encoding($str);

注意,mb_detect_encoding()函数第二参数可以设置为”auto”,将根据mbstring.language来扩展。

有两个参数要特别注意一下,一个是internal_encoding,当发生一种编码向另一种编码转换时,如果编码没有指定,可能就使用所谓的内部编码,另外使用mb_xxx字符串函数时,也需要指定编码,否则就是默认的内部编码。比如:

string mb_convert_encoding ( string $str , string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ] )

这个函数的$from_encoding默认使用内部编码,意思是从$from_encoding转换成$to_encoding,如果字符串的编码跟内部编码不一样,调用时又没有指定$from_encoding(一般应该指定),这里就乱套了。

另一个参数是detect_order,它指定了字符检测的顺序,比如:

string mb_detect_encoding ( string $str [, mixed $encoding_list = mb_detect_order() [, bool $strict = false ]] )

检测字符的编码,第二参数给出检测列表,如果没有指定就是默认值。看如下例子:

<?php
$encodings = array(
	//ascii
	'ASCII',
		
	//unicodes
	'UTF-8',
	'UTF-16',
		
	//chinese
	'EUC-CN',  //gb2312
	'CP936',   //gbk
	'EUC-TW',  //big5
		
	//japanese
	'EUC-JP',
	'SJIS',
	'eucJP-win',
	'SJIS-win',
	'JIS',
	'ISO-2022-JP'
);
	
$s = "PHP程序设计";
$res = mb_detect_encoding($s,$encodings);
if($res){
	echo $res."\n";
}else{
	echo "Can not detect.\n";
}

echo "-------------------------\n";

$res = mb_detect_encoding($s);
if($res){
	echo $res."\n";
}else{
	echo "Can not detect.\n";
}

//文件编码设置成gb2312,所以声明的字符串也是gb2312编码
//输出
/usr/local/php-5.5.15/bin/php m.php
EUC-CN
-------------------------
Can not detect.

不管是转换编码还是多字节字符操作,你最好清楚原字符串的编码。对于未知的字符编码,你最后先检测编码,然后再操作。

为了能最大化检测编码,可以使用如下程序:

$encoding = mb_detect_encoding($str,mb_list_encoding());

当我使用如上代码检测一个gb2312编码的中文字符时,结果给出的是EUC-JP,大爷的,因为EUC-JP在mb_list_encoding()输出中排在前面,编码规则又符合EUC-JP,所以直接就把它返回了,所以,字符串编码检测无法做到精确(??)

模块mbstring还提供了一系列的mb_ereg_xxx函数,PHP中除了提供PCRE正则函数,还提供了POSIX Regex,这些就是ereg_xxx函数,这些函数是非二进制安全的,并且在PHP5.3+以后的版本标记为deprecated(过时),在mbstring中提供了POSIX Regex对应的多字节操作版本。由于PCRE正则函数可以操作多字节字符串,所以一般都不建议再使用ereg_xxx和mb_ereg_xxx函数。如下例子:

//使用gb2312 或 utf-8 编码源文件
$ar = array();
preg_match("/.*(中文)/","PHP程序设计中文版本",$ar);

print_r($ar);

//输出
/usr/local/php-5.5.15/bin/php m.php
Array
(
    [0] => PHP程序设计中文
    [1] => 中文
)

讨论到这里可见,mbstring模块的主要作用在于检测和转换编码,提供对应的多字节操作的字符串函数。不过就单转换编码来说,使用PHP默认启用的iconv模块的提供的函数更加实用和保险,因为mbstring默认是不启用的。

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1166.html

PHP函数参考 – Web服务 – SOAP

PHP的Web服务SOAP扩展需要libxml的支持,跟http://blog.ifeeline.com/1157.html这里描述的一样。这里不再重复。

PHP的SOAP扩展支持SOAP1.1和SOAP1.2,它们都可以以WSDL和NO WSDL方式运行。

要使用PHP的SOAP扩展,需要在编译PHP时需要加入–enable-soap参数。由于SOAP会向客户端传递一个描述文件(使用wsdl时),可以把描述文件缓存起来,相关的配置(以下是默认值):

soap.wsdl_cache_enabled	1	
soap.wsdl_cache_dir	/tmp	
soap.wsdl_cache_ttl	86400	
soap.wsdl_cache		1	
soap.wsdl_cache_limit	5

在测试时最好把soap.wsdl_cache_enabled设置为0。

PHP中的SOAP可分两部分,一部分是作为客户端是消费,一部分是作为服务端提供服务。本文主要探讨客户端:

$webservice_url = "http://******/services/order?wsdl";
$userToken = '******';

$client = new SoapClient($webservice_url);

print_r($client->__getFunctions());
print_r($client->__getTypes());

这里拿了个现成的例子测试,看看都有哪些函数和类型:

/usr/local/php-5.5.15/bin/php soap.php 
Array
(
    [0] => deleteOrderResponse deleteOrder(string $userToken, long $orderId)
    [1] => auditOrderResponse auditOrder(string $userToken, long $orderId)
    [2] => createOrderResponse createOrder(string $userToken, createOrderRequest $createOrderRequest)
    [3] => createOrderResponse createAndAuditOrder(string $userToken, createOrderRequest $createOrderRequest)
    [4] => getTransportWayListResponse getTransportWayList(string $userToken)
)
Array(......)

这里看到服务端提供了哪些API以及如何调用这些API,还有这些API要发送的数据结构和返回的数据结构也有清晰定义。这个是如何做到的?

实际上,__getFunctions()和__getTypes()函数只是解析从服务端返回的XML文件(WSDL)而已。服务端的程序需要构建这个XML文档并定义对应的可调用的方法。参看下图:

在构建SoapClient对象时就可以传递服务地址(wsdl),也可以调用SoapClient::__setLocation指定;通过调用SoapClient::__setCookie设置随SOAP请求一起发送的cookie;通过调用SoapClient::__setSoapHeaders设置SOAP头部信息,注意这个不是请求的头部信息,是Soap的头部,在发起请求时,实际传递一个XML字符串,PHP SOAP扩展提供了一个专门的SoapHeader类,可以自定义一些信息作为XML字符串的一部分发送:

$client = new SoapClient($webservice_url,array('trace' => 1));
$header = new SoapHeader('http://soapinterop.org/echoheader/', 
                            'echoMeStringRequest',
                            'hello world');
$client->__setSoapHeaders($header);

echo $client->__getLastRequest();

这个会输出:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://service.hop.service.ws.hlt.com/" xmlns:ns2="http://soapinterop.org/echoheader/"><SOAP-ENV:Header><ns2:echoMeStringRequest>hello world</ns2:echoMeStringRequest></SOAP-ENV:Header><SOAP-ENV:Body><ns1:getTransportWayList><userToken>2a924b2173d845dab94054223a110339</userToken></ns1:getTransportWayList></SOAP-ENV:Body></SOAP-ENV:Envelope>

注意看这里的SOAP-ENV:Header标签,跟SOAP-ENV:Body并列。具体需要参看SoapHeader类。

继续看如下例子:

$webservice_url = "http://******/services/order?wsdl";
$userToken = '******';

$client = new SoapClient($webservice_url,array('trace' => 1));
$something = $client->getTransportWayList($userToken);

echo "\n----\n".$client->__getLastRequestHeaders();
echo "\n----\n".$client->__getLastRequest();
echo "\n----\n".$client->__getLastResponseHeaders();
echo "\n----\n".$client->__getLastResponse();

这里的四个函数主要是查看请求头部和请求内容 和 响应头部和响应内容,需要注意的是如果要使用这四个方法有效,必须在建立SoapClient对象时,它的第二参数的数组有trace为true,比如array((‘trace’ => 1))。

输出:

----
POST /xms/services/order HTTP/1.1
Host: ****:8086
Connection: Keep-Alive
User-Agent: PHP-SOAP/5.5.15
Content-Type: text/xml; charset=utf-8
SOAPAction: ""
Content-Length: 326


----
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://service.hop.service.ws.hlt.com/"><SOAP-ENV:Body><ns1:getTransportWayList><userToken>*****5dab9******</userToken></ns1:getTransportWayList></SOAP-ENV:Body></SOAP-ENV:Envelope>

----
HTTP/1.1 200 OK
Server: nginx/1.4.4
Date: Mon, 11 Aug 2014 10:41:55 GMT
Content-Type: text/xml;charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: JSESSIONID=6AF7237052D518B02B37D45CF9F84120; Path=/xms/; HttpOnly

----
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns1:getTransportWayListResponse xmlns:ns1="http://service.hop.service.ws.hlt.com/"><return><success>true</success>......

头部输出没有什么特别的,就是HTTP的请求和响应头,不过要注意Content-Type,它是text/xml。请求体是一个XML字符串,里面包含了需要调用的方法和方法的参数。响应体也是一个XML,里面包含了响应的数据。实际我们在调用方法时,SOAP扩展会把请求转换成XML请求体,而服务端收到后把XML进行解析,然后调用对应方法,而返回的XML数据在客户端收到后也进行了对应的转换。

下面谈一下具体的方法调用。

以上代码有:

$client->getTransportWayList($userToken);

实际上,SoapClient()类并没有getTransportWayList()方法,这个方法是来自服务端的,通过调用SoapClient::__getFunctions方法可以看到这个方法,这里这个方法的调用告诉我们,通过wsdl文件暴露的方法,都可以通过SoapClient类对象直接调用,这个调用内部会转换成一个发送XML的HTTP请求。getTransportWayList()这类来自服务端的方法也可以通过调用SoapClient::__soapCall来间接调用。

SoapClient最后需要了解的方法是SoapClient::__doRequest,自己的类可以继承SoapClient然后重写这个方法,那么就可以实现自定义发送请求的操作,默认是自动的(调用方法就开始)。

客户端在调用方法时如果发生了错误该如何捕获?PHP SOAP提供了一个is_soap_fault()的方法,方法参数是调用的结果。需要首先知道的是,SOAP的调用如果发生错误,那么它将抛出一个SoapFault对象的异常,但是如果在创建SoapClient对象时如果第二参数的数组指定了exception为false,比如array(‘exceptions’ => 0),那么当SOAP的调用发生错误时直接返回SoapFault对象,它封装了相关的错误信息。以下官方的例子:

<?php
$client = new SoapClient("some.wsdl", array('exceptions' => 0));
$result = $client->SomeFunction();
if (is_soap_fault($result)) {
    trigger_error("SOAP Fault: (faultcode: {$result->faultcode}, faultstring: {$result->faultstring})", E_USER_ERROR);
}
?>

#################################
<?php
try {
    $client = new SoapClient("some.wsdl");
    $result = $client->SomeFunction(/* ... */);
} catch (SoapFault $fault) {
    trigger_error("SOAP Fault: (faultcode: {$fault->faultcode}, faultstring: {$fault->faultstring})", E_USER_ERROR);
}
?>

SOAP的服务器端部分如果使用WSDL,主要是这个文件的编写,鉴于PHP习惯用来作为SOAP的客户端,所以这里不探讨PHP SOAP的服务端的内容了,附上一篇之前写的文章作为参考:PHP Web服务 SOAP 快速入门

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1159.html

PHP函数参考 – Web服务 – XML-RPC

PHP函数参考 – Web服务 – XML-RPC

从PHP4开始,XML-RPC就已经可用了,现在是PHP5.6都要到达稳定版本了,但是这个XML-RPC扩展的函数还声称是实验性的,实在令人不解。

实际上,要在PHP中实现XML-RPC,不一定要使用PHP自带的这个XML-RPC扩展。XML-RPC是构建在HTTP之上的,数据传递是依靠XML,所以使用PHP来自己实现也是没有问题的,而也的确存在这样的第三方库,不过这里想探讨的还是PHP自带的这个XML-RPC扩展。

由于需要解析XML,所以PHP的libxml扩展是必须的(默认就是启用的,除非明确使用disable-libxml),PHP的libxml扩展实际是libxml2库的包装器,所以需要确保安装了:

rpm -qa | grep xml
libxml2-2.7.6-14.el6_5.2.x86_64
libxml2-devel-2.7.6-14.el6_5.2.x86_64

在编译安装PHP时一般使用–with-libxml-dir=/usr来明确指定libxml安装的地方。

要在PHP中使用XML-RPC,还要在编译时添加–with-xmlrpc。

首先简单说明一下工作过程:客户端使用POST方法把一个XML文档提交过来,这个文档包含了要调用的方法和参数,然后服务端获取这个XML文档并解析出对应的方法和参数,然后调用对应的方法,把结果返回给客户端。对于客户端,只要能构建POST用的XML(当然要按照XML-RPC的XML构建规则)就可以,而没有限制需要使用什么语言。客户端的XML作为POST请求体的一部分,一般可以使用$GLOBALS[‘HTTP_RAW_POST_DATA’]获取原始的POST数据。

例子:

###xmlrpc_server.php
<?php
function xmlrpc_test_func($method, $params) {
        $prms = '';
        if(is_array($params[0])){
                $params = $params[0];
        }
        foreach($params as $pi=>$pv){
                $prms .= $pi."=>".$pv."--";
        }
        $prms = rtrim($prms,'--');
        $return = "Call the server method is $method, and params are:$prms\n";
        return $return;
}

$xmlrpc = xmlrpc_server_create();

xmlrpc_server_register_method($xmlrpc, "xmlrpc_test", "xmlrpc_test_func");

$request = $GLOBALS['HTTP_RAW_POST_DATA'];

$xmlrpc_response = xmlrpc_server_call_method($xmlrpc, $request, null);

header("Content-Type: text/xml");
echo $xmlrpc_response;

xmlrpc_server_destroy($xmlrpc);


###xmlrpc_client.php
<?php
function rpc_use_socket_call($host, $port, $rpc_server, $request) {
        $fp = fsockopen($host, $port);

        $query = "POST $rpc_server HTTP/1.0\nUser_Agent: XML-RPC Client\nHost: ".$host."\nContent-Type: text/xml\nContent-Length: ".strlen($request)."\n\n".$request."\n";

        if (!fputs($fp, $query, strlen($query)))
        {
                $errstr = "Write error";
                return false;
        }

        $contents = "";
        while (!feof($fp)){
                $contents .= fgets($fp);
        }
        fclose($fp);
        return $contents;
}

$host  = "nginx.vfeelit.com";
$port  = 80;
$rpc_server = "/xmlrpc_server.php";

$request = xmlrpc_encode_request("xmlrpc_test", array("test","data"=>"post data","key"=>"121256"));

$response = rpc_use_socket_call($host, $port, $rpc_server, $request);

print_r($response);

xmlrpc_server_create()建立XML-RPC服务端句柄,xmlrpc_server_register_method()注册供客户端调用的方法,第一参数是XML-RPC服务端句柄,第二参数是客户端端调用的方法名字,第三参数是客户端调用方法的映射方法,就是实际将在服务端被执行的方法。xmlrpc_server_call_method()第一参数是XML-RPC服务端句柄,第二参数来自客户端的XML字符串,第三个参数是自定义数据。这个方法会自动解码来自客户端的XML字符串,然后调用这个XML中指定的方法,在这里客户端调用的方法是xmlrpc_test,实际被影射到xmlrpc_test_func,这个方法稍微有点特殊,它的第一个参数是客户端实际调用的方法名字,第二个参数是来自客户端的参数,把方法的执行结果以XML结构的组织返回。

客户端中使用xmlrpc_encode_request()把要调用的方法和参数传递进去,然后这个方法把这些数据装换成一个XML字符串,通过SOCKET的方法,POST过去,不使用xmlrpc_encode_request()方法也可以,不过要按照要求组装XML。当然,不用SOCKET也是可以的,比如在浏览器端用JS也可以一样POST数据。

输出结果:

HTTP/1.1 200 OK
Server: nginx/1.6.0
Date: Sun, 03 Aug 2014 05:29:16 GMT
Content-Type: text/html
Connection: close
X-Powered-By: PHP/5.5.15

<?xml version="1.0" encoding="iso-8859-1"?>
<methodResponse>
<params>
 <param>
  <value>
   <string>Call the server method is xmlrpc_test, and params are:0=&#62;test--data=&#62;post data--key=&#62;121256&#10;</string>
  </value>
 </param>
</params>
</methodResponse>

这里看到,响应头被输出了。接下来是响应的内容,它是XML结构的字符串。所以客户端获取这个XML之后还需要解析这个XML获取数据。从整个流程下来看XML-RPC的使用实际是非常简单的。

在客户端代码中的:

$request = xmlrpc_encode_request("xmlrpc_test", array("test","data"=>"post data","key"=>"121256"));
//输出$request

直接输出:

<?xml version="1.0" encoding="iso-8859-1"?>
<methodCall>
<methodName>rpc_server</methodName>
<params>
 <param>
  <value>
   <struct>
    <member>
     <name>test</name>
     <value>
      <string>123</string>
     </value>
    </member>
    <member>
     <name>getdata</name>
     <value>
      <string>base64data</string>
     </value>
    </member>
   </struct>
  </value>
 </param>
</params>
</methodCall>

这个就是要发送的XML,如果自己组装,也要按照这样的规则,服务端也是按照同样的规则解析这个XML的。

对于轻量级别Web服务开发,PHP的XML-RPC还是简单实用的。不过由于它还是实验性的,版本升级上有一些风险,看起来使用SOAP可能更好一点。不过目前比较流行OAuth,这些PHP中都提供支持(OAuth需要安装PECL包)。

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1157.html

PHP函数参考 – 影响 PHP 行为的扩展 – PHP选项/信息

这里的函数汇总基本可以认为是杂项函数,命名不规范应该是历史原因造成的,已经无法改变。有些函数还是比较有用的,特意摘录一些备忘:

1 bool dl ( string $library )
运行时载入一个 PHP 扩展。(动态载入模块的情况应该避免吧)
2 bool extension_loaded ( string $name )
检查一个扩展是否已经加载。其中一个应用场景是判断一个扩展是否已经加载,没有加载就使用dl()来动态加载:

if (!extension_loaded('gd')) {
    if (!dl('gd.so')) {
        exit;
    }
}

3 array get_defined_constants ([ bool $categorize = false ] )
返回所有常量的关联数组,键是常量名,值是常量值。$categorize为true是按照分类输出。

4 array get_extension_funcs ( string $module_name )
返回模块函数名称的数组。

5 string get_include_path ( void )
获取当前的 include_path 配置选项。这是一个比较有用的函数,对应有一个set_include_path(),可以在运行时修改include_path,这对于要自定义加载类库来说,提供了一个动态修改的途径,还有一个restore_include_path()的方法,可以还原include_path。

$appPath = implode(PS, $paths);
set_include_path($appPath . PS . get_include_path());

6 string get_cfg_var ( string $option )
获取 PHP 配置选项的值。对应还有ini_get()和ini_get_all()函数。如果要动态修改配置值(必须是可以在运行时设置的配置项),可以使用ini_set()函数,如果要还原就用ini_restore()。

@ini_set('display_errors', TRUE);

7 array get_included_files ( void )
返回被 include 和 require 文件名的 array

8 string getenv ( string $varname )
获取一个环境变量的值。可以用$_ENV或$_SERVER替代。如果要设置一个环境变量(只存在当前请求),可以用putenv()函数。

9 string php_ini_loaded_file ( void )
取得已加载的 php.ini 文件的路径。还有一个php_ini_scanned_files()的函数。

10 string php_sapi_name ( void )
返回 web 服务器和 PHP 之间的接口类型

11 string php_uname ([ string $mode = “a” ] )
返回运行 PHP 的系统的有关信息

12 bool phpinfo ([ int $what = INFO_ALL ] )
输出关于 PHP 配置的信息。如果指定参数可以根据分类返回部分数据。如果要检查版本信息,可以调用phpversion()函数。对于版本比较,PHP提供了version_compare()函数。

phpinfo();
phpinfo(8);//仅获取加载的模块还模块配置信息

error_reporting(version_compare(PHP_VERSION, 5.3, '>=') ? E_ALL & ~E_DEPRECATED & ~E_NOTICE : version_compare(PHP_VERSION, 6.0, '>=') ? E_ALL & ~E_DEPRECATED & ~E_NOTICE & ~E_STRICT : E_ALL & ~E_NOTICE);

13 void set_time_limit ( int $seconds )
设置脚本最大执行时间。

14 int zend_thread_id ( void )
返回当前线程的唯一识别符。如果要知道当前PHP的Zend引擎版本,可以使用zend_version()函数。

永久链接:http://blog.ifeeline.com/1155.html

PHP函数参考 – 影响 PHP 行为的扩展 – 输出缓冲控制

输出缓冲控制 是PHP内核的一部分。简单的说就是先把所有内容先写到缓冲中,然后再做整体处理。比如:

ob_start();
readfile($url);
$img = ob_get_contents();
ob_end_clean();

$size = strlen($img);

$fp = fopen($filename , "a");
fwrite($fp, $img);
fclose($fp);

这里把readfile()返回的内容写入缓冲区(而不是直接输出),然后通过ob_get_contents()获取缓冲区内容,然后在对获取到的内容做相应的处理(这里是写入文件),这些处理可以是添加点东西,替换点东西等。

由于这个内容不多,直接参考文档即可。

永久链接:http://blog.ifeeline.com/1153.html

PHP函数参考 – 影响PHP行为的扩展 – 错误处理

PHP的错误处理属于PHP核心的一部分。配置指令:

error_reporting         NULL	设置错误级别
display_errors          "1"	是否显示错误
display_startup_errors  "0"	 
log_errors              "0"	是否记录日志	 
log_errors_max_len      "1024"	日志长度
ignore_repeated_errors  "0"
ignore_repeated_source  "0"
report_memleaks         "1"
track_errors            "0"
xmlrpc_errors           "0"
xmlrpc_error_number     "0"
docref_root             ""
docref_ext              ""
error_prepend_string    NULL 
error_append_string     NULL 
error_log               NULL	错误日志记录位置

以上的配置指令主要的是error_reporting、display_errors、log_errors和error_log。error_reporting设置了报告的类型,log_errors决定了是否记录错误,记录的地方由error_log指定,display_errors决定是否显示错误信息。错误处理的配置指令都可以在运行中通过init_set()修改。对于错误报告类型,还可以在运行中使用error_reporting()修改:

ini_set('error_reporting', E_ALL);
error_reporting(E_ALL);
##PHP 5.3以下版本,E_ALL并没有包含E_STRICT,所以如果要报告所有错误,保险做法如下
error_reporting(E_ALL | E_STRICT);

如果你曾经使用过PHP的try语句,或者你会有疑问,错误和异常有何不一样?

简单来说,try块中的代码如果有抛出异常,可以被catch截获。但是发生错误还是会发送给PHP的错误处理程序。比如如下这段程序:

function doSomeThing($var){
    throw new Exception("Please stop hitting me");
 
}
 
try{
    $f = file("/www/test.txt");
    $a = 10/0;
    doSomeThing($a);
}catch(Exception $e){
    echo "----->Exception";
}

这里面同时发生了PHP错误和抛出异常。PHP错误可能会记录到错误日志,而异常被catch捕获进行处理。(注意,如果发生比较严重的错误,比如语法错误,PHP直接中断解析,后面的方法不会被执行,也就不会有异常抛出了),PHP错误的处理默认是有PHP本身的默认程序处理的,不过可以使用set_error_handler方法来设置一个自定义的错误处理方法。

由于我们将使用set_error_handler函数设置一个自定义错误处理程序来替代PHP标准的错误处理程序,所以对这个函数用法必须来个彻底认识。

mixed set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] )

$error_handler设置回调函数,这个函数设置是有要求的,具体内容可参阅PHP文档,注意这个回调函数如果返回了false,则PHP的标准错误处理程序继续运行。我们看文档中的关于这个函数的描述:

“ 本函数可以用你自己定义的方式来处理运行中的错误, 例如,在应用程序中严重错误发生时,或者在特定条件下触发了一个错误(使用 trigger_error()),你需要对数据/文件做清理回收。

重要的是要记住 error_types 里指定的错误类型都会绕过 PHP 标准错误处理程序(默认是E_ALL | E_STRICT,就是全部错误类型都有自定义函数处理,否则没有包含的由PHP标准程序处理), 除非回调函数返回了 FALSE。 error_reporting() 设置将不会起到作用而你的错误处理函数继续会被调用 —— 不过你仍然可以获取 error_reporting 的当前值,并做适当处理(这里说的是error_reporting设置是针对标准错误处理程序的,它对自定义的错误处理程序无效)。 需要特别注意的是带 @ error-control operator 前缀的语句发生错误时,这个值会是 0(PHP中可以使用@前缀来强制一个语句不报告错误,实际的实现是把error_reporting设置为0,那么标准错误处理程序就不会处理错误,但是这个情况在使用了自定义错误处理程序时无效,有些错误自定义处理程序是无法处理的,实际上有些错误先于自定义错误处理程序前触发,它实际还是使用标准处理程序,所以@字符仍使用意义)。

同时注意,在需要时你有责任使用 die()。 如果错误处理程序返回了,脚本将会继续执行发生错误的后一行(注意这个,错误处理程序返回后脚本继续执行)。

以下级别的错误不能由用户定义的函数来处理: E_ERROR、 E_PARSE、 E_CORE_ERROR、 E_CORE_WARNING、 E_COMPILE_ERROR、 E_COMPILE_WARNING,和在 调用 set_error_handler() 函数所在文件中产生的大多数 E_STRICT(这些级别不能使用自定义函数处理,实际上这些错误根本无法进入到自定义的错误处理程序,这些错误在PHP内核或编译时就被捕获,至于这些错误的处理则是由php.ini文件中的配置决定的,所以当在开发时,最好在php.ini中把display_errors调整为1,error_reporting调整为E_ALL | E_STRICT)。

如果错误发生在脚本执行之前(比如文件上传时),将不会 调用自定义的错误处理程序因为它尚未在那时注册。 ”

从上面的描述来看,我们总结一下:
比较严重的错误,自定义程序无法处理(实际是没有进入这个程序),自定义处理程序只能处理一般错误。一旦使用自定义处理程序来处理错误,那么error_reporting的设置对它是没有作用的(它只处理指定错误处理程序时给定的错误级别)。在开发时,为了能够显示和记录自定义程序无法处理的错误(比如严重错误),应该在php.ini中配置:

display_errors On
error_log /path/to/log/log.txt
error_reporting E_ALL | E_STRICT

注意error_reporting为E_ALL是表示所有错误,但是在PHP5.3中不包含E_STRICT,从PHP5.4开始才包含。display_errors表示从标准错误输出中输出错误,error_log指出记录错误日志的路径。

事实上,我们可以在脚本中改变这些值配置值(而不使用自定义错误处理函数),比如修改error_log,让它把错误记录到我们的指定的日志文件中。比如:http://blog.ifeeline.com/105.html中描述的就是这个情况,但是它只能记录一般错误,对于严重错误,还是根据php.ini配置文件的的设置去处理的。

关于使用set_error_handler()自定义错误处理的例子,可以参考:http://blog.ifeeline.com/690.html,这是一个应用自定义错误处理函数的绝好例子。

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1147.html