PHP のマルチバイト関数の標準化について PHP Internals に質問および提案するために、調査したことおよび自分の意見をまとめました。
概要
このドキュメントでは標準関数の strlen
、substr
、chr
、ord
のマルチバイトの文字エンコーディング対応もしくはこれらに相当する新しい標準関数の導入について検討します。
取り組む目的は CMS、フレームワーク開発のための必要最小限の関数を提供することで、mbstring、iconv、intl への依存度を下げるもしくはこれらのモジュールのポリフィルライブラリが UTF-8 以外のより多くのマルチバイトの文字エンコーディングに対応できるようにすることです。
標準のマルチバイト関数が存在しないことの課題
mbstring、iconv、intl がインストールされていない環境のために主要な CMS やフレームワークは独自のマルチバイト関数のライブラリを開発したり (Drupal)、フォールバック関数のパッケージを配布しています (Symfony)。
文字エンコーディングのシェア
2010年に公開された Google の記事によると約50%のウェブサイトの文字エンコーディングが UTF-8 です。
選択肢
次の選択肢が考えられます。
- 標準関数の
strlen
、substr
、chr
、ord
に文字エンコーディングオプションを追加する (デフォルトの文字エンコーディングはISO-8859-1
もしくはUTF-8
) - 新しい名前空間つきの
strlen
、substr
、chr
、ord
(デフォルトの文字エンコーディングはUTF-8
) - 名前空間なしで
strlen
、substr
、chr
、ord
以外の関数 (デフォルトの文字エンコーディングはUTF-8
)
選択肢 1 を選ぶ場合、デフォルトの文字エンコーディングの選択が問題になります。mbstring によるオーバーロード対策から学ぶことができます。
UTF-8
を選ぶ場合、後方互換性が失われるので、フレームワーク・ライブラリの対応が必要になります。後方互換性を保つために ISO-8859-1
を選ぶ場合、UTF-8
を使うには毎回引数に指定する必要があります。
選択肢 2 を選ぶ場合、新しい名前空間の候補を検討する必要があります。
選択肢 3 を選ぶ場合、長い名前、広く使われない名前を選ぶ必要があります。
strlen、substr、chr、ord に相当する関数、クラス
mbstring
mb_strlen
mb_substr
mb_convert_encoding
mb_encode_numericentity
mb_decode_numericentity
iconv
iconv_strlen
iconv_substr
iconv
intl
grapheme_extract
IntlBreakIterator
IntlCodePointBreakIterator
UConverter
IntlChar::chr
IntlChar::ord
ポリフィルの事例
- WordPress: compat.php -
mb_strlen
とmb_substr
- Joomla - phputf8
- Drupal: Unicode クラス
- MediaWiki: Fallback
- Symfony: Polyfill コンポーネント - mbstring、iconv、intl の各種関数の実装
コードポイントと文字の相互変換。
- WordPress -
wp_encode_emoji
- HTMLPurifier -
unichr
文字からコードポイントを求めて、JavaScript エスケープシーケンスに変換する事例として次のプロジェクトが挙げられます。Escaping RFC for PHP Core も参照。JSON エスケープシーケンスを求めるには json_encode
を使うこともできます。
- Composer - JsonFormatter
- Zend Escaper - jsMatcher
- Twig - twig_escape_filter
マルチバイト対応の標準関数の現状
PHP 5.3.0 で PCRE モジュールが標準化され、UTF-8 および拡張書記素クラスターを扱う関数を定義することが手軽になりました。mbstring、iconv の関数とも遜色ない処理速度なので、ポリフィルライブラリの実装にも広く使われています。preg_match
を使った例はこちら、preg_replace_callback
を使った例はこちらの記事を参照ください。
utf8_decode
は strlen
と組み合わせて、文字数を数えるために使われることがあります。
$str = 'あいうえお';
$length = strlen(utf8_decode($str);
htmlspecialchars
および htmlspecialchars_decode
は複数の文字エンコーディングに対応しており、この2つの関数を組み合わせてさまざまなマルチバイト関数を定義することは可能ですが、実用的な速度ではありません。コードの例はこちらをご参照ください。
コードポイントから文字への変換には html_entity_decode
を使うことができます。拡張モジュールを含めたコードの例に関して、次の記事をご参照ください。
C 言語のマルチバイト対応の標準関数
C 言語の標準関数の中にはマルチバイトに対応しているものがあります (詳しい内容はこちら)。しかしながら、ロケールに依存する問題や不正なバイト列を扱えない課題を抱えているために、実装に使うことはできません。ただし、自前の文字列関数を定義する際の参考にはなります。UTF-8 の不正なバイト列の扱いの実装に関しては、こちらの記事をご参照ください。
提案
名前空間ありの関数
新しい名前空間とともに次の関数を提案します。
int strlen ( string $string[, string $encoding = "UTf-8"] )
string substr ( string $string , int $start [, int $length [, string $encoding = "UTF-8"]] )
string chr ( int $codepoint [, string $encoding = "UTF-8"] )
int ord ( string $string[, string $encoding = "UTF-8"] )
デフォルトの文字エンコーディングは UTF-8
とします。mbstring、iconv との互換性のため、htmlspecialchars
がサポートする文字エンコーディングを選べるようにすることが望ましいと考えます。
実装例は次のとおりです。
-
strlen
- UTF-8 限定の実装例 -
substr
- UTF-8 限定の実装例 -
chr
- ord と合わせた実装例 ord
名前空間なしの標準関数
名前空間が受理されない場合の提案です。関数の名前の参考事例として Java や JavaScript が挙げられます。
Java
Java.lang.String.codePointCount
JavaScript
String.prototype.charAt
String.prototype.charCodeAt
String.fromCharCode
-
String.prototype.at
- ES7 への提案 String.prototype.codePointAt
String.fromCodePoint
str_codepoint_count
int str_codepoint_count ( string $string, [, string $encoding = "UTF-8"] )
実装例は名前空間つきの strlen
と同じです。
str_at
string str_at ( string $string, int $index[, string $encoding = "UTF-8"] )
str_codepoint_at
int str_codepoint_at ( string $string, int $index[, string $encoding = "UTF-8"] )
str_from_codepoint
string str_from_codepoint (array $codepoints[, string $encoding = "UTF-8"] )
不正なバイト列の扱いに関する方針
不正なバイト列に遭遇したとき、処理を続けるか null
を返すのか方針を決める必要があります。htmlspecialchars
や mbstring の場合、処理を続け、PCRE や intl の場合、null
を返します。
マルチバイト文字を扱うための PHP API
すでに UTF-8 を扱うためのための PHP API が存在します(ext/standard/html.c)。
php_next_utf8_char
php_next_utf8_char
は get_next_char
のショートカットで utf8_decode
の実装に使われています。get_next_char
はhtmlspecialchars
の実装に使われています。
mbstring や iconv がサポートする多くのマルチバイトの文字エンコーディングを選べるように、get_next_char
および determine_charset
を static から PHP API に変更することを提案します。
php_get_next_char
php_determine_charset
その他
1文字ずつコールバックを適用する関数
str_at
、str_codepoint_at
と並行して次の関数も検討対象として挙げます。
これらの関数の参考例として Ruby の String クラスの each_char
、each_codepoint
が挙げられます。
str_each_char
の別の名前の候補として str_replace_callback
を挙げます。PHP の似たような関数の先行事例として preg_replace_callback
と mb_ereg_replace_callback
があります。
str_each_char
str_each_codepoint
str_each_char
void str_each_char ( string $string[, callable $callback [, string $encoding = "UTF-8" ]] )
str_each_codepoint
void str_each_codepoint ( string $string[, callable $callback [, string $encoding = "UTF-8" ]] )
部分文字列を取り出す関数
関数の名前の用例は Rust、Groovy (StringGroovyMethods)、Haskell (List) があります。
Rust
-
truncate
- std::string::String
Groovy の StringGroovyMethods
- take
- drop
- takeWhile
- dropWhile
str_take
str_take_while
str_drop
str_drop_while
文字列イテレーター
一文字取り出す方法として、イテレーターのクラスの導入も挙げられます。
StringIterator
CodePointIterator
不正なバイト列を扱うための関数
余裕があれば、不正なバイト列を扱うための関数も提案します。
str_check_encoding
str_scrub
str_check_encoding
の先行事例は mb_check_encoding
、str_scrub
は Ruby の String クラスの scrub
です。
filter モジュール
提案される標準関数に対応する filter モジュールのフィルターを追加することも検討できます。
FILTER_VALIDATE_STRING_LENGTH
最小文字数と最大文字数の検証フィルター。実装例
FILTER_VALIDATE_ENCODING
文字エンコーディングが妥当であるかの検証フィルター。実装例
FILTER_ESCAPE_ILL_FORMED_BYTES
不正なバイト列を代替文字に置き換える除去フィルター。実装例
mbstring への新しい関数
標準関数への提案と並行して、mbstring にも対応する関数も提案します。逆に mbstring で先に導入して試してから、標準関数に導入することを提案するというシナリオも考えられます。
mb_chr
PHP 7.2 で導入される予定です。
mb_ord
PHP 7.2 で導入される予定です。
mb_at
mb_codepoint_at
mb_from_codepoint
mb_each_char
mb_each_codepoint
mb_scrub
PHP 7.2 で導入される予定です。
mb_take
mb_drop
mbstring がサポートする文字エンコーディングの中にはコードポイントと文字の相互変換の用途に想定していないものがあるので、それらの文字エンコーディングは変換の対象として禁止にします。