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

**【2021/10/15 追記】**
**この記事は更新が停止されています。現在では筆者の思想が変化している面もありますので，過去の記事として参考程度にご覧ください。**

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

`basename()` について
---------------------------

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

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

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

マルチバイト対応 `explode()`
----------------------------------

```text:UTF-8限定版
explode() を使えば問題ない。
```

```php:全エンコーディング対応版
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()`
----------------------------------

```php: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** を返します。

```php:全エンコーディング対応版
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()`
-----------------------------

```php: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** を返します。

```php:全エンコーディング対応版
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()`
------------------------------------

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

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

マルチバイト対応 `str_replace()`
-------------------------------------

```text:UTF-8限定版
str_replace() を使えば問題ない。
```

```php:全エンコーディング対応版
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()`
------------------------------

```text:UTF-8限定版シグネチャ
string strtr_utf8 ( string $str , string $from , string $to )
string strtr_utf8 ( string $str , array $replace_pairs )
```
```php: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);
}
```

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

```text:全エンコーディング対応版シグネチャ
string mb_strtr ( string $str , string $from , string $to [, string $encoding] )
string mb_strtr ( string $str , array $replace_pairs [, string $encoding] )
```
```php:全エンコーディング対応版コード
<?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つ)

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