Mac环境PHP踩过的“坑” (一)函数重载

零、问题¶

  在Mac下开发本来很高兴,可是有一个地方把人恶心到了,Apache+Mod_PHP方式,出现了一个strtoupper('汉字')乱码的问题。。结果一个

  strtoupper('法克'); 输出乱码,把我搞了一个下午。。。。

 一、问题定位(源码分析)¶

 上代码(摘自ZF1):

 

# \Zend_Locale_Format::_parseDate line 866-871
# 这里有这么一段精彩绝伦的转换(判定时间格式"2015-05-07 a18:33:32"里面是否含有一个a,如果有则表示上午,否则下午:Zend大哥,为嘛要那么多strtoupper?代码还不够长?)
// get daytime
if (iconv_strpos($format, 'a') !== false) {
    /*对,就是这里,挂掉*/  
    if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'am'))) !== false) {
        $am = true;
    } else if (iconv_strpos(strtoupper($number), strtoupper(Zend_Locale_Data::getContent($options['locale'], 'pm'))) !== false) {
        $am = false;
    }
}

  百思不得其解,于是bing/google/baddu....stackoverflow。。。依然无解,然后继续xdebug单步调试仍然无所收获,毫无进展,无奈祭出翻源码大法:

PHPAPI char *php_strtoupper(char *s, size_t len)
{
        unsigned char *c, *e;

        c = (unsigned char *)s;
        e = (unsigned char *)c+len;

        while (c < e) {
                *c = toupper(*c);
                c++;
        }
        return s;
}

  看到这么简洁的代码,我竟无言以对!!把这段代码给深谙C的G大哥,可这总不能改源码修复这个bug吧?是PHP源码的问题吗?就在G大哥搞出一个办法时,小伙伴H兄duang的一下找到问题方案了,果然思路正确结果OK。事后总结,不要试图找太多系统代码因素(Zend怎么会不清楚呢?),任何PHP (Linux)的问题,总归要先看下配置,纯找strtoupper的问题或者locale的问题一概搜不到接近真相的可能 。

二、解决方法(配置php.ini) ¶

Mac下面需要显式的设置两个参数: 

; internal/script encoding.
; Some encoding cannot work as internal encoding.
; (e.g. SJIS, BIG5, ISO-2022-*)
; http://php.net/mbstring.internal-encoding
;mbstring.internal_encoding = UTF-8
; 不显式指定的话,Mac下会默认 ISO-8859-1 (好差异,说好的默认值呢。。)
mbstring.internal_encoding = UTF-8 

; overload(replace) single byte functions by mbstring functions.
; mail(), ereg(), etc are overloaded by mb_send_mail(), mb_ereg(),
; etc. Possible values are 0,1,2,4 or combination of them.
; For example, 7 for overload everything.
; 0: No overload
; 1: Overload mail() function
; 2: Overload str*() functions
; 4: Overload ereg*() functions
; http://php.net/mbstring.func-overload
;mbstring.func_overload = 0
mbstring.func_overload = 2


 再次谢过身边的小伙伴,you are the best!!

 PS:感谢官方关于string的解释 http://php.net/manual/zh/language.types.string.php 

       以及函数重载的故事(对,这是我从业8年第一次认真了解这个事情!!) http://php.net/manual/zh/mbstring.overload.php 

       其实他们有说过,不要试图。。。。所以啊,这些个“坑”都真的是“坑”么?

  附上摘录,懒惰的哥们可以不用点连接了:

PS1:字符串类型详解 ¶

PHP 中的 string 的实现方式是一个由字节组成的数组再加上一个整数指明缓冲区长度。并无如何将字节转换成字符的信息,由程序员来决定。字符串由什么值来组成并无限制;特别的,其值为 0(“NUL bytes”)的字节可以处于字符串任何位置(不过有几个函数,在本手册中被称为非“二进制安全”的,也许会把 NUL 字节之后的数据全都忽略)。

字符串类型的此特性解释了为什么 PHP 中没有单独的“byte”类型 - 已经用字符串来代替了。返回非文本值的函数 - 例如从网络套接字读取的任意数据 - 仍会返回字符串。

由于 PHP 并不特别指明字符串的编码,那字符串到底是怎样编码的呢?例如字符串 "á" 到底是等于 "\xE1"(ISO-8859-1),"\xC3\xA1"(UTF-8,C form),"\x61\xCC\x81"(UTF-8,D form)还是任何其它可能的表达呢?答案是字符串会被按照该脚本文件相同的编码方式来编码。因此如果一个脚本的编码是 ISO-8859-1,则其中的字符串也会被编码为 ISO-8859-1,以此类推。不过这并不适用于激活了 Zend Multibyte 时;此时脚本可以是以任何方式编码的(明确指定或被自动检测)然后被转换为某种内部编码,然后字符串将被用此方式编码。注意脚本的编码有一些约束(如果激活了 Zend Multibyte 则是其内部编码)- 这意味着此编码应该是 ASCII 的兼容超集,例如 UTF-8 或 ISO-8859-1。不过要注意,依赖状态的编码其中相同的字节值可以用于首字母和非首字母而转换状态,这可能会造成问题。

当然了,要做到有用,操作文本的函数必须假定字符串是如何编码的。不幸的是,PHP 关于此的函数有很多变种:

  • 某些函数假定字符串是以单字节编码的,但并不需要将字节解释为特定的字符。例如 substr(),strpos(),strlen()和 strcmp()。理解这些函数的另一种方法是它们作用于内存缓冲区,即按照字节和字节下标操作。

  • 某些函数被传递入了字符串的编码方式,也可能会假定默认无此信息。例如 htmlentities() 和 mbstring 扩展中的大部分函数。

  • 其它函数使用了当前区域(见 setlocale()),但是逐字节操作。例如 strcasecmp(),strtoupper() 和 ucfirst()。这意味着这些函数只能用于单字节编码,而且编码要与区域匹配。例如 strtoupper("á") 在区域设定正确并且 á 是单字节编码时会返回 "Á"。如果是用 UTF-8 编码则不会返回正确结果,其结果根据当前区域有可能返回损坏的值。

  • 最后一些函数会假定字符串是使用某特定编码的,通常是 UTF-8。intl 扩展和 PCRE(上例中仅在使用了 u 修饰符时)扩展中的大部分函数都是这样。尽管这是由于其特殊用途,utf8_decode() 会假定 UTF-8 编码而utf8_encode() 会假定 ISO-8859-1 编码。

最后,要书写能够正确使用 Unicode 的程序依赖于很小心地避免那些可能会损坏数据的函数。要使用来自于 intl 和mbstring 扩展的函数。不过使用能处理 Unicode 编码的函数只是个开始。不管用何种语言提供的函数,最基本的还是了解 Unicode 规格。例如一个程序如果假定只有大写和小写,那可是大错特错。

  

PS2:函数重载功能 ¶

你也许常常会发现现存的 PHP 应用很难运行在多字节环境下。 发生这种情况的原因是大多数那种 PHP 应用使用了标准的字符串函数,类似 substr(),已知无法处理多字节编码的字符串。

mbstring 支持一个“函数重载”功能,将对应的多字节版本重载到标准字符处理函数上,例如你能够让这类应用在不修改代码的前提下添加多字节的处理能力。 比如,启用函数重载后,mb_substr() 将会代替 substr() 被调用。 在很多情况下这个功能允许让仅支持单字节编码的应用简单地和多字节环境对接。


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




你可能感兴趣的:(Mac环境PHP踩过的“坑” (一)函数重载)