LoginSignup
69
65

More than 5 years have passed since last update.

namespaceとBOMに何の関係があるのさ

Last updated at Posted at 2015-09-24

先日公開したモダンPHPアンチパターンの記事で、「namespaceを利用しない」の項目に「BOMが出力された怖い話」を書いたところ、いまいち理解されてない感があるので、地面に吐いた唾を飲み込む心意気で自分のギャグをことこまかに紹介したい。

過去に承認欲求欲しさに自分の靴を舐めたシリーズ

さておき、実際の例を見せる

説明の前に、本稿の内容が有効であることをコード例をもって示す。使用したバージョンはOS X標準添付のPHP 5.5.27(cli)だ。

以下のコードはわかりやすさのため、Z Shellで実行する。エスケープめんどくさいので…

% echo 'hello' | php
hello
% echo 'hello\n<?php' | php
hello
% echo '<?php echo "hello", PHP_EOL;' | php
hello
% echo '<?php namespace dummy; echo "hello", PHP_EOL;' | php
hello
% echo 'hello\n<?php namespace dummy;' | php

Fatal error: Namespace declaration statement has to be the very first statement in the script in - on line 2
% echo '<?php namespace dummy; echo "hello", PHP_EOL;' | nkf --oc=UTF-8-BOM | php

Fatal error: Namespace declaration statement has to be the very first statement in the script in - on line 1

この例ではUTF-8にBOMを付加するためにnkfコマンドを利用した。

BOMとは何か

バイトオーダーマーク (byte order mark) あるいはバイト順マーク(バイトじゅんマーク)は通称BOM(ボム)といわれる、Unicode符号化形式で符号化したテキストの先頭につける数バイトのデータのことである。このデータを元にUnicodeで符号化されていることおよび符号化の種類の判別に使用する。

(バイトオーダーマーク - Wikipedia 2013-03-20T15:26:14‎版より引用)

要はUnicodeで符号化されたテキストファイルの先頭にあるバイトで、これを読み取ることによってエンコーディングの種類(UTF-16, UTF-32, UTF-8)とバイトオーダー(ビッグエンディアン, リトルエンディアン)を判別できる。これはUTF-8にも規定されてるのだけれども、実際のところUTF-8には不必要なものであって基本的には付けるべきではない。BOMなしのUTF-8をUTF-8Nと呼ぶ謎の慣習(?)がある(謎)。

PHPとテンプレートエンジン

PHPはチューリング完全性とオブジェクト指向機能を持った、世界でもっとも著名なテンプレートエンジンである… とジョーク1はさておいて、曲りなりにもテンプレートエンジンなので、<?php … ?>の外に書かれた文字列は、そのまま出力される。

これは換言すると、次のような説明が成り立つ。

以下のa.phpb.phpのコードは実質等価である。

a.php
<!DOCTYPE html>
<title>てすと
<p>みなさん
<?php if (1 == 2) { ?>
こんにちは
<?php } else { ?>
さようなら
<?php }
b.php
<?php
echo "<!DOCTYPE html>\n<title>てすと\n<p>みなさん\n";
if (1 == 2) {
    echo "こんにちは\n";
} else {
    echo "さようなら\n";
}

おわかりいただけるだろうか。

PHPと名前空間

先に見せたPHPコマンドの実行結果を再度掲載する。

% echo '<?php namespace dummy; echo "hello", PHP_EOL;' | nkf --oc=UTF-8-BOM | php

Fatal error: Namespace declaration statement has to be the very first statement in the script in - on line 1

致命的エラーのメッセージを良く読んでいただきたい。

また、前回の「モダンPHPアンチパターン」にも引いたPHP: 名前空間 - Manualの子記事であるPHP: 名前空間の定義 - Manualからも一節を引用する。

名前空間を宣言するには、キーワード namespace を使用します。名前空間を含むファイルでは、他のコードより前にファイルの先頭で名前空間を宣言しなければなりません。 ただし declare キーワードは例外です。

(php.net - PHP: 名前空間の定義 - Manualより引用 2015年9月24日閲覧)

なるほど、有効なPHPソースコードでは、namespace定義より前にはdeclare文しか書くことは許されない。

これまでの記事の説明に従って解釈すれば、ワンライナーのソースコードは次のように変形できる。

x.php
[BOM]<?php namespace dummy; echo "hello", PHP_EOL;
y.php
<?php
echo "[BOM]";
namespace dummy;
echo "hello", PHP_EOL;

(ここではわかりやすさのために、UTF-8のBOM0xEF 0xBB 0xBF[BOM]と表記した。)

x.php, y.phpともに、namespaceよりも前にecho文が登場してしまった。よって、これらのプログラムはPHPのソースコードとしてSyntax Errorである。

ついでにJSONの話

JSONの最新仕様であるRFC 7159 - The JavaScript Object Notation (JSON) Data Interchange Formatによれば、JSONのSyntaxは以下の通りだ。

2.  JSON Grammar

   A JSON text is a sequence of tokens.  The set of tokens includes six
   structural characters, strings, numbers, and three literal names.

   A JSON text is a serialized value.  Note that certain previous
   specifications of JSON constrained a JSON text to be an object or an
   array.  Implementations that generate only objects or arrays where a
   JSON text is called for will be interoperable in the sense that all
   implementations will accept these as conforming JSON texts.

      JSON-text = ws value ws

   (中略)

   Insignificant whitespace is allowed before or after any of the six
   structural characters.

      ws = *(
              %x20 /              ; Space
              %x09 /              ; Horizontal tab
              %x0A /              ; Line feed or New line
              %x0D )              ; Carriage return
   (後略)

JSONにとってBOMはホワイトスペースではないし、後略したそのほかのリテラルにとって意味のある文字でもない。

次に、同じ文書から文字エンコーディングについての節も抜萃する。

8.1.  Character Encoding

   JSON text SHALL be encoded in UTF-8, UTF-16, or UTF-32.  The default
   encoding is UTF-8, and JSON texts that are encoded in UTF-8 are
   interoperable in the sense that they will be read successfully by the
   maximum number of implementations; there are many implementations
   that cannot successfully read texts in other encodings (such as
   UTF-16 and UTF-32).

   Implementations MUST NOT add a byte order mark to the beginning of a
   JSON text.  In the interests of interoperability, implementations
   that parse JSON texts MAY ignore the presence of a byte order mark
   rather than treating it as an error.

まとめると「相互運用性のために推奨するエンコーディングはUTF-8だよ」「JSON処理系はBOMを付加しちゃアカンよ」「JSON処理系は互換性のためにBOMをエラーにしないで無視するかもね」って感じですね。

あるJSON処理系は先頭バイトのBOMをSyntax Errorとして怒るし、別のある処理系は空気を読んで見逃してくれるってことですね。

namespaceの効能(副作用)

上記までの内容を総合することで、UTF-8であるにも拘らずBOMを付加しようとする不届きなエディタで編集されたソースコードはnamespaceを導入することで一律Syntax Errorとみなすことが可能になり、無意味なBOMを先頭バイトに持ったJSONレスポンスを出力することを抑止できます。

BOMを出力しない意味

「そんな、UTF-8にBOMが含まれたってデメリットはないし、プログラマーが気持ち悪いって騒ぐだけなんでしょ」この指摘は半分合ってるし、半分間違ってる。

もしあなたのWebアプリケーションがContent-Type: text/htmlだけを返すならば、それは間違ってない。Content-Type: application/jsonを返す場合においても、JSONパーサーが空気を読んでくれる場合において実害は発生しない。

ところが、テキストではないファイルを出力する用事に駆られたときはどうだろう。

bomgif.php
<?php

header('Content-Type: image/gif');
// UTF-8 BOM
echo json_encode(hex2bin('efbbbf'));
// Base64エンコードされた1x1のspacer.gif
echo base64_decode('R0lGODlhAQABAPAAAP8AAAAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==');

このファイルを保存して php -S localhost:3939 でサーバーを起動し、ブラウザで http://localhost:3939/bomgif.php を開いてみよう。

ファイルの性質によるものの、無意味なBOMを含んだファイルをうまく扱ってくれるバイナリフォーマットの処理系は多くないような気がしてる。

まとめ

自分の靴を舐めるつもりで書いたけど、意外と説明することが多かった。あと一発ギャグを説明することで精神につらさがこみあげてきた。

さて、鎮守府に着任しますか ヾ(〃><)ノ゙☆


  1. 返すがへすも、これはジョークである 

69
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
65