20
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

PHP のマルチバイト関数標準化のための取り組み

PHP のマルチバイト関数の標準化について PHP Internals に質問および提案するために、調査したことおよび自分の意見をまとめました。

概要

このドキュメントでは標準関数の strlensubstrchrord のマルチバイトの文字エンコーディング対応もしくはこれらに相当する新しい標準関数の導入について検討します。

取り組む目的は CMS、フレームワーク開発のための必要最小限の関数を提供することで、mbstring、iconv、intl への依存度を下げるもしくはこれらのモジュールのポリフィルライブラリが UTF-8 以外のより多くのマルチバイトの文字エンコーディングに対応できるようにすることです。

標準のマルチバイト関数が存在しないことの課題

mbstring、iconv、intl がインストールされていない環境のために主要な CMS やフレームワークは独自のマルチバイト関数のライブラリを開発したり (Drupal)、フォールバック関数のパッケージを配布しています (Symfony)。

文字エンコーディングのシェア

2010年に公開された Google の記事によると約50%のウェブサイトの文字エンコーディングが UTF-8 です。

選択肢

次の選択肢が考えられます。

  1. 標準関数の strlensubstrchrord に文字エンコーディングオプションを追加する (デフォルトの文字エンコーディングは ISO-8859-1 もしくは UTF-8)
  2. 新しい名前空間つきの strlensubstrchrord (デフォルトの文字エンコーディングは UTF-8)
  3. 名前空間なしで strlensubstrchrord 以外の関数 (デフォルトの文字エンコーディングは 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_strlenmb_substr
  • Joomla - phputf8
  • Drupal: Unicode クラス
  • MediaWiki: Fallback
  • Symfony: Polyfill コンポーネント - mbstring、iconv、intl の各種関数の実装

コードポイントと文字の相互変換。

文字からコードポイントを求めて、JavaScript エスケープシーケンスに変換する事例として次のプロジェクトが挙げられます。Escaping RFC for PHP Core も参照。JSON エスケープシーケンスを求めるには json_encode を使うこともできます。

マルチバイト対応の標準関数の現状

PHP 5.3.0 で PCRE モジュールが標準化され、UTF-8 および拡張書記素クラスターを扱う関数を定義することが手軽になりました。mbstring、iconv の関数とも遜色ない処理速度なので、ポリフィルライブラリの実装にも広く使われています。preg_match を使った例はこちらpreg_replace_callback を使った例はこちらの記事を参照ください。

utf8_decodestrlen と組み合わせて、文字数を数えるために使われることがあります。

$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 がサポートする文字エンコーディングを選べるようにすることが望ましいと考えます。

実装例は次のとおりです。

名前空間なしの標準関数

名前空間が受理されない場合の提案です。関数の名前の参考事例として 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"] )

UTF-8 限定の実装例

str_codepoint_at

int str_codepoint_at ( string $string, int $index[, string $encoding = "UTF-8"] )

UTF-8 限定の実装例

str_from_codepoint

string str_from_codepoint (array $codepoints[, string $encoding = "UTF-8"] )

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_charget_next_char のショートカットで utf8_decode の実装に使われています。get_next_charhtmlspecialchars の実装に使われています。

mbstring や iconv がサポートする多くのマルチバイトの文字エンコーディングを選べるように、get_next_char および determine_charset を static から PHP API に変更することを提案します。

  • php_get_next_char
  • php_determine_charset

その他

1文字ずつコールバックを適用する関数

str_atstr_codepoint_at と並行して次の関数も検討対象として挙げます。
これらの関数の参考例として Ruby の String クラスの each_chareach_codepoint が挙げられます。

str_each_char の別の名前の候補として str_replace_callback を挙げます。PHP の似たような関数の先行事例として preg_replace_callbackmb_ereg_replace_callback があります。

str_each_char

UTF-8 限定の実装例

str_each_codepoint

UTF-8 限定の実装例

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

UTF-8 限定の実装例

str_take_while

UTF-8 限定の実装例

str_drop

UTF-8 限定の実装例

str_drop_while

UTF-8 限定の実装例

文字列イテレーター

一文字取り出す方法として、イテレーターのクラスの導入も挙げられます。

StringIterator

UTF-8 限定の実装例

CodePointIterator

UTF-8 限定の実装例

不正なバイト列を扱うための関数

余裕があれば、不正なバイト列を扱うための関数も提案します。

str_check_encoding

UTF-8 限定の実装例

str_scrub

UTF-8 限定の実装例

str_check_encoding の先行事例は mb_check_encodingstr_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 がサポートする文字エンコーディングの中にはコードポイントと文字の相互変換の用途に想定していないものがあるので、それらの文字エンコーディングは変換の対象として禁止にします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
20
Help us understand the problem. What are the problem?