PHP 字符串截取函数

PHP就有函数实现字符串截取substr,mb_substr,mb_strimwidth
1.substr遇到中文可能就会乱码,因为1个中文字符占3个位(指占3个字母的长度)
2.mb_substr可以截取中文字符串,不过截取出来的宽度不一样(2个字母占一个中文字符的宽度)
3.mb_strimwidth这个函数我使用起来总觉得不对劲,width为什么要+3才能开始截取?如果width写太少它就会乱取

以上种种问题,我就想自己写个函数,解决字符串不会乱码,宽度不会有偏差。
1.第一个问题,substr会乱码,我看过emlog有个函数解决了这个问题,大致是截取后算出其中包含的中文字符长度,因为中文字符占3位,缺少几位就补几位就可以。

/**
 * 截取编码为utf8的字符串
 *
 * @param string $strings 预处理字符串
 * @param int $start 开始处 eg:0
 * @param int $length 截取长度
 */
function subString($strings, $start, $length) {
    if (function_exists('mb_substr') && function_exists('mb_strlen')) {
        $sub_str = mb_substr($strings, $start, $length, 'utf8');
        return mb_strlen($sub_str, 'utf8') < mb_strlen($strings, 'utf8') ? $sub_str . '...' : $sub_str;
    }
    $str = substr($strings, $start, $length);
    $char = 0;
    for ($i = 0; $i < strlen($str); $i++) {
        if (ord($str[$i]) >= 128)
            $char++;
    }
    $str2 = substr($strings, $start, $length + 1);
    $str3 = substr($strings, $start, $length + 2);
    if ($char % 3 == 1) {
        if ($length <= strlen($strings)) {
            $str3 = $str3 .= '...';
        }
        return $str3;
    }
    if ($char % 3 == 2) {
        if ($length <= strlen($strings)) {
            $str2 = $str2 .= '...';
        }
        return $str2;
    }
    if ($char % 3 == 0) {
        if ($length <= strlen($strings)) {
            $str = $str .= '...';
        }
        return $str;
    }
}

// 自己写的带有注释函数(上面函数式是从emlog复制过来的)
// 其实把有mb_*的函数,就没必要自己写这个函数了,写这个函数只是为了兼容没有mb_*函数时(就像ci里面,core/compare里面会有一些其它版本的兼容函数)
function str_limit($string, $start, $length)
{
    $str = substr($string, $start, $length);
    
    // 应该是计算出中文字符长度(可能是ord出>=128就是中文字符的一部分,这里注意1个中文字符占3位)
    $char = 0;
    for ($i = 0; $i < $length; $i++) {
         if (ord($str[$i]) >= 128) {
             $char++;
         }
    }
    
    // 由于中文字符是占3位,那么截取出来的可能是1位都不缺,或者缺1位,或者缺2位
    $str1 = substr($string, $start, $length + 1);
    $str2 = substr($string, $start, $length + 2);
    if ($char %3  == 0) {
        if ($length <= strlen($string)) {
            $str .= '...';
        }
        return $str;
    }
    if ($char % 3 == 1){
        if ($length <= strlen($string)) {
            $str2 .= '...';
        }
        return $str2;
    }
    if ($char % 3 == 2){
        if ($length <= strlen($string)) {
            $str1 .= '...';
        }
        return $str1;
    }
}
var_dump(str_limit("nihao你好呀的重庆", 0, 10));

// 解决中英文字符串截取宽度问题
// (用法 str_limit($string, 2*x, '...')其中2表示一个中文字符的长度,也就是说以中文字符长度为准)
function str_limit($string, $length, $end = '...')
{
    // 已经截取的字符串
    $str = '';
    // 已经截取到的长度
    $len = 0;
    // 计算最后1个中文字符已经截取到第几位了
    $endLen = 0;
    // 被截取字符串的长度
    $strLen = strlen($string);
    // 循环字符串长度去截取
    for ($i = 0; $i < $strLen; $i++) {
        // 没有字符串可截取就跳出去
        if (!isset($string[$i])) {
            break;
        }
        // 如果已经截取完成(中文字符可能直接进2位,所以这里要写>=号)
        if ($len >= $length) {
            break;
        }
        // 把截取到字符拼接到另一个变量,计算该字符是中文字还是英文字,如果是中文字需截取到3位才能算2个长度(也就是说最后1个字符的长度可能超出了预料的长度)
        $str .= $string[$i];
        if (ord($string[$i]) >= 128) {
            $endLen++;
            if ($endLen == 3) {
                $len += 2;
                $endLen = 0;
            }
        } else {
            $len++;
        }
    }
    
    // 上面截取最后1个字符可能超出预算,因为最后1个字符可能是中文
    // 所以最多超出2个位(3位表示1个中文字符),1个中文字符等于2个字母长度,误差也就是1个字母长度
    // 这里也就没必去为了整个长度就1个字母长度的误差,再次计算,性能不划算(后面有时间看看再怎么优化把,其实已经满足现在的需求了,有取就有舍的道理,如反过来想,如果截取的最后一个字符是中文,那么原本却只要1的字母的长度,那么最后一个中文字符就得丢掉,也就会丢一个字母长度)
    
    // 处理被截取的字符串长度 大于 截取的长度,后面拼接...
    if (isset($string[$i + 1])) {
        $str .= $end;
    }
    return $str;
}