解决 PHP 中 usort 在值相同时改变原始位置的问题

从 PHP 4.1.0 后,usort 在比较的值相同时,原始位置可能会改变,文档中是这样说的:

If two members compare as equal, their order in the sorted array is undefined.

也就是说,如果比较的2个值相同,则它们在排序结果中的顺序是随机的。如果你需要保持相同值的原始位置,可以参考本文的方法。

演示数据:

<?php
/*
解决 PHP 中 usort 在值相同时改变原始位置的问题
作者:Artlover    http://www.CodeBit.cn
*/
$arr = array(
	array('a' => 5, 'b' => 3),
	array('a' => 5, 'b' => 1),
	array('a' => 5, 'b' => 4),
	array('a' => 5, 'b' => 2),
);
?>

数组中第一个元素的值是相同的,期望的结果是保持现有的位置不变,即 b 的顺序为 3,1,4,2

用 usort 排序,当比较字段的值相同时,原始顺序可能会改变

<?php
/*
解决 PHP 中 usort 在值相同时改变原始位置的问题
作者:Artlover    http://www.CodeBit.cn
*/
$callback = create_function('$a,$b', 'return ($a["a"] == $b["a"])?0:(($a["a"] > $b["a"]) ? 1 : -1);');
usort($arr, $callback);
?>

结果:


Array
(
    [0] => Array
        (
            [a] => 5
            [b] => 2
        )
 
    [1] => Array
        (
            [a] => 5
            [b] => 4
        )
 
    [2] => Array
        (
            [a] => 5
            [b] => 1
        )
 
    [3] => Array
        (
            [a] => 5
            [b] => 3
        )
 
)

虽然排序字段的值相同,但是 usort 却将整个数组的顺序打乱了。

如果要在比较的值相同时保持原始位置,可以用 array_multisort :

<?php

/*
解决 PHP 中 usort 在值相同时改变原始位置的问题
作者:Artlover    http://www.CodeBit.cn
*/

// 索引计数器
$i = 0;

// 创建2个空数组,第一个保存要排序的字段,第二个保存原始索引信息
$a = $index = array();

foreach ($arr as $key => $data) {
	$a[$key] = $data['a'];
	$index[] = $i++;
}

// 第一个数组先排,接着按原始索引排
array_multisort($a, SORT_ASC, $index, SORT_ASC, $arr);

?>

结果:


Array
(
    [0] => Array
        (
            [a] => 5
            [b] => 3
        )
 
    [1] => Array
        (
            [a] => 5
            [b] => 1
        )
 
    [2] => Array
        (
            [a] => 5
            [b] => 4
        )
 
    [3] => Array
        (
            [a] => 5
            [b] => 2
        )
 
)

PHP 中检查或过滤 IP 地址

网络环境异常复杂,有时候我们不得不禁止一些恶意用户访问,禁止的方式有很多种,其中一种就是通过 IP 来限制,本文提供的方法允许你通过 IP 区间、CIDR (Classless Inter-Domain Routing)及单个 IP 格式来检查或过滤 IP 地址。

你可以通过增加一个配置文件,然后将需要禁止的一些 IP 地址通过一定规则添加到配置文件中,在程序初始化的时候,读取配置文件中的每个规则,然后通过本文提供的方法去检查当前访问的客户端 IP 地址是否存在于这些规则中,如果存在,则拒绝提供服务。

<?php
/**
 * PHP 中检查或过滤 IP 地址
 * 
 * 支持 IP 区间、CIDR(Classless Inter-Domain Routing)及单个 IP 格式
 * 整理:http://www.CodeBit.cn
 * 参考:
 *   - {@link http://us2.php.net/manual/zh/function.ip2long.php#70055}
 *   - {@link http://us2.php.net/manual/zh/function.ip2long.php#82397}
 * 
 * @param string $network 网段,支持 IP 区间、CIDR及单个 IP 格式
 * @param string $ip 要检查的 IP 地址
 * @return boolean
 */
function netMatch($network, $ip) {

    $network = trim($network);
    $ip = trim($ip);
    
    $result = false;
    
    // IP range : 174.129.0.0 - 174.129.255.255
    if (false !== ($pos = strpos($network, "-"))) {
        $from = ip2long(trim(substr($network, 0, $pos)));
        $to = ip2long(trim(substr($network, $pos+1)));
        
        $ip = ip2long($ip);
        
        $result = ($ip >= $from and $ip <= $to);

    // CIDR : 174.129.0.0/16
    } else if (false !== strpos($network,"/")) {
        list ($net, $mask) = explode ('/', $network);
        $result = (ip2long($ip) & ~((1 << (32 - $mask)) - 1)) == ip2long($net); 

    // single IP
    } else {
        $result = $network === $ip;
    
    }
    
    return $result;
}

// 174.129.0.0 - 174.129.255.255
var_dump(netMatch(' 174.129.0.0  -  174.129.255.255 ', '174.129.1.31')); // True
var_dump(netMatch(' 174.129.0.0/16 ', '174.139.1.31')); // False
var_dump(netMatch(' 174.129.1.32 ', '174.129.1.31')); // False
?>

由于中国使用的大多数都是动态 IP 地址,所以通过 IP 地址限制访问具有一定的局限性,使用的时候需要谨慎,但是对于应急限制访问来说,还是非常有用的。

PHP 的 array_diff() 函数在处理大数组时的效率问题

PHP 5.2.6 以上版本的 array_diff() 函数在处理大数组时,需要花费超长时间,这个 bug 已经被官方确认;在这个问题被修复之前或者在我们不能控制 PHP 版本的时候,可以使用本文提供的方法。

cisa 提交到 PHP 官方 BUG 页面上的方法

<?php
/**
 * 解决 php 5.2.6 以上版本 array_diff() 函数在处理
 * 大数组时的需要花费超长时间的问题
 * 
 * 整理:http://www.CodeBit.cn
 * 来源:http://bugs.php.net/47643
 */
function array_diff_fast($data1, $data2) {
    $data1 = array_flip($data1);
    $data2 = array_flip($data2);

    foreach($data2 as $hash => $key) {
       if (isset($data1[$hash])) unset($data1[$hash]);
    }

    return array_flip($data1);
}
?>

根据 ChinaUnix 论坛版主 hightman 思路重写的方法

<?php
/**
 * 解决 php 5.2.6 以上版本 array_diff() 函数在处理大数组时的效率问题
 * 根据 ChinaUnix 论坛版主 hightman 思路写的方法
 * 
 * 整理:http://www.CodeBit.cn
 * 参考:http://bbs.chinaunix.net/viewthread.php?tid=938096&rpid=6817036&ordertype=0&page=1#pid6817036
 */
function array_diff_fast($firstArray, $secondArray) {

    // 转换第二个数组的键值关系
    $secondArray = array_flip($secondArray);

    // 循环第一个数组
    foreach($firstArray as $key => $value) { 

        // 如果第二个数组中存在第一个数组的值
        if (isset($secondArray[$value])) {

            // 移除第一个数组中对应的元素
            unset($firstArray[$key]); 
        } 
    }

    return $firstArray; 
}
?>

此方法只交换了第二个数组的 key 和 value,所以效率更高。

注意:PHP 内置的 array_diff() 函数可以处理多个数组,而本文提供的方法只处理了两个数组的比较。

在 XSLTProcessor 中 registerPHPFunctions 后无法调用 php 函数

XSLT 是一个非常方便的转换 XML 的工具,PHP 里面是通过 XSLTProcessor 来实现;XSLT 中内置了许多有用的函数,同时,只需要调用 XSLTProcessor 实例的 registerPHPFunctions 方法,我们就可以在 XSLT 中直接使用 PHP 的函数,这大大增强了 XSLT 的处理能力。

但是,在 XSLT 中使用 PHP 函数时,很多人会遇到如下两种错误:

(1) Warning: XSLTProcessor::transformToXml(): xmlXPathCompiledEval: 1 objects left o
n the stack.
(2)PHP Warning: XSLTProcessor::transformToXml(): xmlXPathCompOpEval: function func
tion bound to undefined prefix php in ….

示例代码:

 
<?php
$xml = <<<EOB
<allusers>
 <user>
  <uid>bob</uid>
 </user>
 <user>
  <uid>joe</uid>
 </user>
</allusers>
EOB;
$xsl = <<<EOB
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8" indent="yes"/>
 <xsl:template match="allusers">
  <html><body>
    <h2>Users</h2>
    <table>
    <xsl:for-each select="user">
      <tr><td>
        <xsl:value-of
             select="php:function('ucfirst',string(uid))"/>
      </td></tr>
    </xsl:for-each>
    </table>
  </body></html>
 </xsl:template>
</xsl:stylesheet>
EOB;
$xmldoc = DOMDocument::loadXML($xml);
$xsldoc = DOMDocument::loadXML($xsl);
 
$proc = new XSLTProcessor();
$proc->registerPHPFunctions();
$proc->importStyleSheet($xsldoc);
echo $proc->transformToXML($xmldoc);
?>

其实,出现这种错误,是因为我们没有定义 PHP namespace ,只需要在

 
<xsl:stylesheet version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

中增加 xmlns:php="http://php.net/xsl" 就能解决此问题, 即

 
<xsl:stylesheet version="1.0" 
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:php="http://php.net/xsl">

获取 $_SERVER[“REQUEST_URI”] 值的通用解决方案

在 PHP 众多预定义服务器变量中,$_SERVER["REQUEST_URI"] 算是经常用到的,但是这个变量只有 apache 才支持,因此,我们需要一个更加通用的方式来获取 REQUEST_URI 的值,本文就是结束这一问题的解决方案。

 
<?php
 
// 说明:获取 _SERVER['REQUEST_URI'] 值的通用解决方案
// 来源:drupal-5.1 bootstrap.inc
// 整理:CodeBit.cn ( http://www.CodeBit.cn )
 
function request_uri()
{
	if (isset($_SERVER['REQUEST_URI']))
	{
		$uri = $_SERVER['REQUEST_URI'];
	}
	else
	{
		if (isset($_SERVER['argv']))
		{
			$uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['argv'][0];
		}
		else
		{
			$uri = $_SERVER['PHP_SELF'] .'?'. $_SERVER['QUERY_STRING'];
		}
	}
	return $uri;
}
 
?>

在数值中加入千位分隔符的方法

为了方便阅读,我们通常会在一串长数字中加入“千位分隔符”,即将 1234567890.11 转换成 1,234,567,890.11 ,这样的任务交给正则表达式来处理再方便不过了,本文就介绍了这个问题的解决方法。

语法:

 
(?<=[0-9])(?=(?:[0-9]{3})+(?![0-9]))

使用范例:

 
<?php
 
// 说明:在数值中加入千位分隔符的方法
// 整理:CodeBit.cn ( http://www.CodeBit.cn )
 
$num = "1234567890.11";
 
$num = preg_replace('/(?<=[0-9])(?=(?:[0-9]{3})+(?![0-9]))/', ',', $num); 
 
echo $num;
 
?>

PHP 连接 MSSQL 时 nvarchar 字段长度被截断为 255

许多用 PHP 连接 MSSQL 的新手经常遇到这个问题:数据库里面的 nvarchar 字段中数据一切正常,但是用 PHP 查询出来却发现长度只有 255,我们都知道,在 MySQL 里面 varchar 的长度只有 255,但是 MSSQL 却不是,不会是 PHP 将 nvarchar 按照 MySQL 的 varchar 处理了吧!本文给出了解决方法:

 
select cast(目标字段 as text) from 表名

假如你的 article 表中有个字段 summary 为 nvarchar,那么命令为:

 
select cast(summary as text) from article

关于 cast :

CAST ( expression AS data_type )

expression 为目标字段
data_type 为要转换成的数据类型

PHP 连接 MSSQL 时 text 字段被无故截断

第一次使用 PHP 连接 MSSQL 的朋友经常遇到这个问题:text 字段总是会被无故截断,只能发一两千字,有时截断时还出现乱码情况,而将执行插入的 SQL 语句输出却没有任何问题。呵呵,其实这个问题很简单:

PHP 的配置文件 php.ini 中 MSSQL 部分:

 
[MSSQL]
; Allow or prevent persistent links.
mssql.allow_persistent = On
 
; Maximum number of persistent links.  -1 means no limit.
mssql.max_persistent = -1
 
; Maximum number of links (persistent+non persistent).  -1 means no limit.
mssql.max_links = -1
 
; Minimum error severity to display.
mssql.min_error_severity = 10
 
; Minimum message severity to display.
mssql.min_message_severity = 10
 
; Compatability mode with old versions of PHP 3.0.
mssql.compatability_mode = Off
 
; Connect timeout
;mssql.connect_timeout = 5
 
; Query timeout
;mssql.timeout = 60
 
; Valid range 0 - 2147483647.  Default = 4096.
mssql.textlimit = 4096
 
; Valid range 0 - 2147483647.  Default = 4096.
mssql.textsize = 4096

请看这两个配置参数:

 
; Valid range 0 - 2147483647.  Default = 4096.
mssql.textlimit = 4096
 
; Valid range 0 - 2147483647.  Default = 4096.
mssql.textsize = 4096

相信你现在已经知道了问题的关键所在,只需要将这两个数值改大即可,不过,同时也要考虑:

 
; Maximum size of POST data that PHP will accept.
post_max_size = 2M

需要根据实际情况设置。

PHP4 下递归 (recursive)创建目录(mkdir)的方法(UPDATE!)

PHP4 下递归 (recursive)创建目录(mkdir)的方法一

PHP5 下创建目录函数 mkdir 增加了一个新的参数 recursive ,通过设置 recursive 为 true 可以实现递归创建目录的目的,这个功能在我们不能确保上级目录存在的情况下非常有用,本文介绍了在 PHP4 下实现这一功能的方法。

 
<?php
 
// 说明:PHP4 下递归 (recursive)创建目录(mkdir)的方法
// 整理:http://www.codebit.cn
 
function mkdirs($dir, $mode = 0777)
{
	if (is_dir($dir) || @mkdir($dir, $mode)) return TRUE;
	if (!mkdirs(dirname($dir), $mode)) return FALSE;
	return @mkdir($dir, $mode);
}
 
?>

函数有个可选参数 $mode , 即创建的文件夹的权限,默认为 0777。

PHP4 下递归 (recursive)创建目录(mkdir)的方法二

此方法由 millken 在 【喜悦国际村】论坛中发布,是他从国外的一个缓存系统(PHP-Cache-Kit)中摘录,代码极其精练:

 
<?php
 
// 说明:PHP4 下递归 (recursive)创建目录(mkdir)的方法
// 整理:CodeBit.cn ( http://www.CodeBit.cn )
// 来源:http://acme-web-design.info/php-cache-kit.htm
 
function forceDirectory($dir){ // force directory structure 
	return is_dir($dir) or (forceDirectory(dirname($dir)) and mkdir($dir, 0777));
}
//USAGE:forceDirectory('/a/b/c/d/f');
 
?>

NuSOAP 调用 Web Service 乱码问题及其解决方法

NuSOAP 是 PHP 环境下的 WEB 服务编程工具,用于创建或调用 WEB 服务。它是一个开源软件,完全由PHP语言编写,由一系列 PHP 类组成,不需要扩展库的支持,这种特性使得 NuSOAP 可以用于所有的 PHP 环境,不受服务器安全设置的影响。

许多使用 NuSOAP 调用 .NET Web Service 的朋友可能都遇到过中文乱码问题,本文介绍这一问题的出现的原因和相应的解决方法。

NuSOAP 调用 Web Service 出现乱码的原因:

通常我们进行 Web Service 开发时都是用的 UTF-8 编码,这时我们需要设置 :

 
$client->soap_defencoding = 'utf-8';

同时,需要让 xml 以同样的编码方式传递:

 
$client->xml_encoding = 'utf-8';

到现在,应该是一切正常了才对,但是我们在输出结果的时候,却发现,返回的是乱码。

NuSOAP 调用 Web Service 出现乱码的解决方法:

实际上,开启了调试功能的朋友,相信会发现 $client->response 返回的是正确的结果,为什么 $result = $client->call($action, array(‘parameters’ => $param)); 却是乱码呢?

研究过 NuSOAP 代码后我们会发现,当 xml_encoding 设置为 UTF-8 时,NuSOAP 会检测 decode_utf8 的设置,如果为 true ,会执行 PHP 里面的 utf8_decode 函数,而 NuSOAP 默认为 true,因此,我们需要设置:

 
$client->soap_defencoding = 'utf-8';
$client->decode_utf8 = false;
$client->xml_encoding = 'utf-8';