マルチバイト未対応の関数をいろいろ対応させてみた

  • 36
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

出来るだけもとの関数の挙動に近づけています。

basename() について

PHP4.4.9まではNULLバイトを正しく処理できませんでしたが、
PHP5.0.0以降はバイナリセーフになりました。
(徳丸さんよりご指摘を頂きました)

但し、 setlocale()ロケールを正しく設定していることが前提になります。

エンコーディングが壊れていてもそのまま処理します。

マルチバイト対応 explode()

UTF-8限定版
explode() を使えば問題ない。
全エンコーディング対応版
function mb_explode($delimiter, $string, $limit = -1, $encoding = null) {
    $tmp = mb_regex_encoding();
    mb_regex_encoding(func_num_args() > 3 ? $encoding : mb_internal_encoding());
    $delimiter = mb_ereg_replace('[.\\\\+*?\\[^$(){}|]', '\\\\0', $delimiter);
    $ret = mb_split($delimiter, $string, $limit);
    mb_regex_encoding($tmp);
    return $ret;
}

エンコーディングが壊れていてもそのまま処理します。

マルチバイト対応 str_split()

UTF-8限定版
function str_split_utf8($string, $split_length = 1) {
    switch (true) {
        case ($split_length = (int)$split_length) < 1:
            return false;
        case !preg_match_all("/.{{$split_length}}|.++|\\A\\z/us", $string, $matches):
            return null;
        default:
            return $matches[0];
    }
}

長さ指定が間違っているときは FALSE を返します。
エンコーディングが壊れているときは NULL を返します。

全エンコーディング対応版
function mb_str_split($string, $split_length = 1, $encoding = null) {
    if ($split_length < 1) {
        return false;
    }
    if (func_num_args() < 3) {
        $encoding = mb_internal_encoding();
    }
    $ret = array();
    $len = mb_strlen($string, $encoding);
    for ($i = 0; $i < $len; $i += $split_length) {
        $ret[] = mb_substr($string, $i, $split_length, $encoding);
    }
    if (!$ret) {
        $ret[] = '';
    }
    return $ret;
}

長さ指定が間違っているときは FALSE を返します。
エンコーディングが壊れていてもそのまま処理します。

マルチバイト対応 trim()

UTF-8限定版
function trim_utf8($str, $charlist = " \t\n\r\0\x0B ") {
    $charlist = str_replace('..', '-', addcslashes($charlist, "^-:]\0\\/"));
    return preg_replace("/\\A[{$charlist}]++|[{$charlist}]++\\z/u", '', $str);
}

エンコーディングが壊れているときには NULL を返します。

全エンコーディング対応版
function mb_trim($str, $charlist = " \t\n\r\0\x0B ", $encoding = null) {
    $tmp = mb_regex_encoding();
    mb_regex_encoding(func_num_args() > 2 ? $encoding : mb_internal_encoding());
    $charlist = mb_ereg_replace('[\\[\\]^-]', '\\\\0', $charlist);
    $charlist = mb_ereg_replace('\\.{2}', '-', $charlist);
    $ret = mb_ereg_replace("\\A[{$charlist}]++|[{$charlist}]++\\z", '', $str);
    mb_regex_encoding($tmp);
    return $ret;
}

エンコーディングが壊れていてもそのまま処理します。

ltrim(), rtrim() もこんな感じでどうぞ。

マルチバイト対応 wordwrap()

想像以上に処理が大変だったので今回はギブアップ(汗)
マニュアルのコメント欄に投稿されているものもどれも再現性が微妙なものばかりで、みなさん苦戦されている様子。
いろいろ試してみた結果、 Smartymb_wordwrap() が一番良さげだったので紹介しておきます。
https://github.com/Jamesking56/Smarty-PHP/blob/master/plugins/shared.mb_wordwrap.php

もっと簡単に書けたらいいのになぁ・・・

マルチバイト対応 str_replace()

UTF-8限定版
str_replace() を使えば問題ない。
全エンコーディング対応版
function mb_str_replace($search, $replace, $subject, $encoding = null) {
    $tmp = mb_regex_encoding();
    mb_regex_encoding(func_num_args() > 3 ? $encoding : mb_internal_encoding());
    foreach ((array)$search as $i => $s) {
        if (!is_array($replace)) {
            $r = $replace;
        } elseif (isset($replace[$i])) {
            $r = $replace[$i];
        } else {
            $r = '';
        }
        $s = mb_ereg_replace('[.\\\\+*?\\[^$(){}|]', '\\\\0', $s);
        $subject = mb_ereg_replace($s, $r, $subject);
    }
    mb_regex_encoding($tmp);
    return $subject;
}

エンコーディングが壊れていてもそのまま処理します。

マルチバイト対応 strtr()

UTF-8限定版シグネチャ
string strtr_utf8 ( string $str , string $from , string $to )
string strtr_utf8 ( string $str , array $replace_pairs )
UTF-8限定版コード
function strtr_utf8() {
    if (func_num_args() < 3) {
        list($str, $replace_pairs) = func_get_args();
    } else {
        list($str, $from, $to) = func_get_args();
        $from = preg_split('//u', $from, -1, PREG_SPLIT_NO_EMPTY);
        $to = preg_split('//u', $to, -1, PREG_SPLIT_NO_EMPTY);
        $replace_pairs = array();
        foreach ($from as $i => $f) {
            if (!isset($to[$i])) {
                break;
            }
            $replace_pairs[$f] = $to[$i];
        }
    }
    return strtr($str, $replace_pairs);
}

エンコーディングが壊れていてもそのまま処理します。

全エンコーディング対応版シグネチャ
string mb_strtr ( string $str , string $from , string $to [, string $encoding] )
string mb_strtr ( string $str , array $replace_pairs [, string $encoding] )
全エンコーディング対応版コード
<?php
function mb_strtr() {
    $args = func_get_args();
    if (!is_array($args[1])) {
        list($str, $from, $to) = $args;
        $encoding = isset($args[3]) ? $args[3] : mb_internal_encoding(); 
        $replace_pairs = array();
        $len = mb_strlen($from, $encoding);
        for ($i =0; $i < $len; $i++) {
            $k = mb_substr($from, $i, 1, $encoding);
            $v = mb_substr($to, $i, 1, $encoding);
            $replace_pairs[$k] = $v;
        }
        return $replace_pairs ? mb_strtr($str, $replace_pairs, $encoding) : $str;
    }
    list($str, $replace_pairs) = $args;
    $tmp = mb_regex_encoding();
    mb_regex_encoding(isset($args[2]) ? $args[2] : mb_internal_encoding());
    uksort($replace_pairs, function ($a, $b) {
        return strlen($b) - strlen($a);
    });
    $from = $to = array();
    foreach ($replace_pairs as $f => $t) {
        if ($f !== '') {
            $from[] = '(' . mb_ereg_replace('[.\\\\+*?\\[^$(){}|]', '\\\\0', $f) . ')';
            $to[] = $t;
        }
    }
    $pattern = implode('|', $from);
    $ret = mb_ereg_replace_callback($pattern, function ($from) use ($to) {
        foreach ($to as $i => $t) {
            if ($from[$i + 1] !== '') {
                return $t;
            }
        }
    }, $str);
    mb_regex_encoding($tmp);
    return $ret;
}

エンコーディングが壊れていてもそのまま処理します。
PHP5.4.1 以降のみ対応。

備考

なお文字列置換関数に関して、動作の軽い順に

  • strtr() (引数3つ)
  • str_replace() (配列を含まない)
  • str_replace() (配列を含む)
  • strtr() (引数2つ)

――超えられない壁――

  • strtr_utf8() (引数2つ)
  • strtr_utf8() (引数3つ)

――超えられない壁――

  • mb_str_replace() (配列を含まない)
  • mb_strtr() (引数2つ)
  • mb_str_replace() (配列を含む)
  • mb_strtr() (引数3つ)

だいたいこんな感じのパフォーマンス順位になると思います。
正確なことは検証してないので分かりませんが。