PHP5.4 Advent Calendar 2011 18日目

この記事は 17 日目の@rskyさんに続いて、PHP5.4 Advent Calendar 2011 18日目エントリです。彼の朝はいつ開けるのでしょうか。

さて、このAdvent Calenderではそーだいさんの記事に続き2回目になるhtmlspecialcharsネタ。 そもそもそんな一つの関数に 2回も同じネタやる意味あるのかと悩んだのですが、突撃してみたいとおもいます。言いたいことは大体、先の記事に書いてあるので僕からは、もう少し別の角度からいってみたいと思います。

きっかけ

そもそもこの記事を書こうとおもったきっかけなんですが、久しぶりに、PHPマニュアルの htmlspecialchars のページ開いたときでした。。

string htmlspecialchars ( string $string [, int $flags = ENT_COMPAT | ENT_HTML401 [, string $charset [, bool $double_encode = true ]]] )

第 3 引数を指定するか否かと議論になってますが、double_encodeという引数が、もうずいぶん前に追加されていることに気がつきました。
え、なにそれこわいと、該当ページ下部の変更履歴を見てみると PHP5.2.3 の時代の変更でした。えれぇ昔のものなので僕が気がついてなかっただけのようです。

ちなみにこのオプションの意味は、

double_encode をオフにすると、PHP は既存の html エンティティをエンコードしません。 デフォルトでは、既存のエンティティも含めてすべてを変換します。

とのことで、一度このオプションをoffにしたらhtmlspecialchars_decodeをかけてもほぼほぼ戻せないテキストデータが一丁あがりなオプションがついてました。そもそも可逆が保障されているようなものではないので、まぁそういうものかもしれません。
個人的には、「やつはまだ第2形態だ、第3,第4形態を残しているぞ」という気分になりました。

そういったことがあり、この記事を書いてみようかなと思いました。

PHP5.4の話

あ、そうだ、PHP5.4でしたね。さて、このPHP5.4から$flagsに下記のオプションが追加されています。

定数 ENT_SUBSTITUTE、ENT_DISALLOWED、 ENT_HTML401、ENT_XML1、 ENT_XHTML および ENT_HTML5 が追加されました。

これ結構さくっといってますが、結構気になる箇所です。

ENT_SUBSTITUTE

無効な符号単位シーケンスを含む文字列を渡したときに、 空の文字列を返すのではなく Unicode の置換文字に置き換えます。 UTF-8 の場合は U+FFFD、それ以外の場合は &#FFFD; となります。

ENT_DISALLOWED

指定した文書型において無効な符号単位シーケンスを含む文字列を渡したときに、 Unicode の置換文字に置き換えます。 UTF-8 の場合は U+FFFD、それ以外の場合は &#FFFD; となります。

ちょっと悩んだのですが、t_komuraさんの記事を読んで、なんとなくわかった気になりなおかつ、コミットログソースコードをあさったでは以下のよいうな感じではなかろうかという結論に達しました。

不正なバイト列があったときに、ENT_IGNOREというオプションは削除なのですが新しくできた二つは別の文字列に置き換えます。。
ENT_SUBSTITUTEは指定した文章型によらず不正な文字列を特定のコードに置換します、ENT_DISALLOWEDは指定した文書型(つまり、ENT_HTML401、ENT_XML1、 ENT_XHTML、ENT_HTML5)のオプションに従って処理をします。(ここがちょっとあやしい)ENT_DISALLOWEDは、double_encodeがfalseのとき参照時に推奨されていない数値参照についても同様の処理を行うようになっているようにも見えます。

とりあえずコミットログの下記あたりと参考になると思いますのでこちらを参考にしてください。(だいたい同じこといってます)

Added the flag ENT_SUBSTITUTE, which makes htmlentities()/htmlspecialchars()
replace the invalid multibyte sequences with U+FFFD (UTF-8) or &#FFFD;
(other encodings).

Added the flag ENT_DISALLOWED. Implements FR #52860. Characters that cannot
appear literally are replaced by U+FFFD (UTF-8) or &#FFFD; (otherwise).
An alternative implementation would be to encode those characters into
numerical entities, but that would only work in HTML 4.01 due to limitations
on the values of numerical entities in other document types. See also the
effects on htmlentities()/htmlspecialchars() with !double_encode above.

おそらくここらへんちょっとまだ、全体的に英語からして、(otherwise)の処理が書いてなかったりするあたりから類推するに、完全に文書化されてないところがあるようにみえるので、正式版を見てから考えたいです。*1

こう、解説なのに結論めいたことがかけないですが

  • double_encodeは、falseにしないほうがいいと思う。
  • いままでの挙動変えたくないなら、htmlspecialchars($str, ENT_QUOTES, 'utf-8');という書き方でいいと思う。
  • 新しいルールに従いなおかつENT_SUBSTITUTEと ENT_DISALLOWEDを使わないのであれば、PHP htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'utf-8');(HTML5の場合)で書いておけばいいとおもう。

という感じでが今の僕のなかの結論です。
まぁ結論からいって何も変わらないとおもっていただいて結構です。

そういうわけで

個人的には、htmlspecialchars を書く箇所というのはフレームワーク内で数か所なので修正はそんなに大変ではないだろうし、正しい使い方さえ認識があればそんなに修正箇所が多いものだとはおもっていません。また、いままでは動作を指定していたものが、新しいオプションにより、HTML5等の仕様側を関数が面倒みてくれるところまで踏み込んできたのはいいことのように思えます。
ただ、ちょっと追加されたオプションについては、もうちょっと識者の意見がお伺いしたいところだったりします。

まぁとはいえ、PHP のマニュアルを見るだけでこれだけいろいろ思うことがでてくるといのはいいことでもあり、悪いことでもあると思うのですが、PHPマニュアルの読み方とかPHPマニュアルの面白さが少しでも伝われば幸いです。

最後にここいちは、クリスマスイブ一緒に飲んだくれてくれる女の子を募集中です!
htmlspecialcharsの件、クリスマスの件フィードバックをお待ちしております。

さて、次回はco3kさんです!!

*1:もしくはもう少し追試したいです