ひとつきほど前に書き殴ったけど放置されてたので放流する。
前提
- Unicodeは文字集合であり、文字ごとにコードポイントが割り当てられる
- UTF-8はUnicodeをバイト列として表現するための方式(文字符号化方式 - Wikipedia)のひとつである
- UTF-8は文字によってバイト長が異なる
- UTF-8 - Wikipediaを100回読むこと
- PHPはASCII互換であれば、エンコーディングに依存しない言語である
- PHPの「文字列」は単なるバイト列である
- もちろんPHPの標準ライブラリはマルチバイト文字列処理をサポートする
- 言語コアがエンコーディングをサポートするRubyではソースファイルに不正なバイト列が含まれるとSyntaxErrorだが、PHP5では(Syntaxと矛盾しない限り)そのようなことはない
- 現在のPHPでの標準的なエンコーディングはUTF-8である
- これは現在のWWWではUTF-8が一般的であること、PSR-1のような統一的なコーディング規約においてUTF-8が MUST(しなければならない) として紹介されることによる
- PSR-1 基本コーディング規約(日本語)|北海道札幌市のシステム開発会社インフィニットループ
-
json_encode
に含まれる文字列のエンコーディングはUTF-8でなければなりません
各手法の比較
文字列リテラル
<?php
$wave_dash = '〜';
$full_width_tilde = '~';
-
pros
- 実行時にコストがかからない
- コード上にバイト列が直接埋め込まれてるので
- クラス定数(
const
)に定義することが可能
- 実行時にコストがかからない
- cons
JSON
Introducing JSONを読め。
JSONでは\u
とUnicodeのコードポイント16進数4桁でユニコード文字を表現することができるので、これを利用する。
<?php
$wave_dash = json_decode('"\u301C"');
$full_width_tilde = json_decode('"\uFF5E"');
-
pros
- Unicodeを正しく解釈できないエディタ/処理系に殺されることはない
- メソッド呼び出し一回で済む
-
cons
- クラス定数(
const
)に定義することができない -
'"xxx"'
みたいに二重括弧で括るのがわかりにくいし、コンパイラレベルのエラーにならない - 文字列を取得したいために
json_decode
って書いてるのが直感的ではなくバッドノウハウくささがある
- クラス定数(
chr-implode
<?php
$wave_dash = implode('', array_map('chr', [0357, 0275, 0236]));
$full_width_dash = implode('', array_map('chr', [0343, 0200, 0234]));
-
pros
- Unicodeを正しく解釈できないエディタ/処理系に殺されることはない
- PHPの文字列が単なるバイナリ列だと理解してると直感的
- JSONモジュールが無効な環境でも動作する
-
cons
- クラス定数(
const
)に定義することができない - 一文が長く、周りくどく見える
- クラス定数(
まとめ
- なんかこのへんの知見が共有されてないんじゃないかと思ったので書いた
- ぶっちゃけどれでもいい
- ただし、
- 筆者は
const
に定義したいのでリテラルで書くことにした - 数も多くないのでPHPUnitで
chr-implode
を使って作った文字列と比較して、不埒者にリテラルを破潰されるリスクを未然に防ぐことにした
- 筆者は
- 個人的にはJSON形式で書くとUnicode文字列、
chr-implode
で書くとUnicode文字であることを強調できる気がする(気分の問題)
あとがき
TEditorの功罪について誰か語ってください。人類がJISコードの重力井戸の底に居た頃はこの上なく素晴らしかったのですが…
2014年の現在において、Unicode/UTF-8対応を謳っておきながら、JIS X 0208範囲外の文字を認識できないエディタの存在は邪悪だと、はっきり申し上げたい。
おまけ バイト列の数値表現を調べる
本文中ではUnicodeの16進表記とUTF-8の8進表記を使った。
array_map("ord", str_split("〜")));
//=> array (
// 0 => 227,
// 1 => 128,
// 2 => 156,
// )
array_map(function($n){ return "0".decoct(ord($n)); }, str_split("〜"));
//=> array (
// 0 => '0343',
// 1 => '0200',
// 2 => '0234',
// )
)
json_encode("〜", ~JSON_UNESCAPED_UNICODE);
# => '"\\u301c"'