124
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

なぜPHPファイルは <?php が省略できないのか

Posted at

短い答え:歴史的経緯

別の答え:実装すればできなくはない (が、望みは薄い…)

PHPが<?php開始タグを要求する理由

元々PHPはHTMLに埋め込むテンプレートエンジンのようなものだったからです。

Rasmusが紹介する1994年、1995年、2004年のPHPコード(イメージ)

1994's PHP Code
1995's PHP Code
2005's PHP Code

現代のバージョンでも動く、PHPによるHTMLテンプレートのイメージはこのような感じでしょうか:

html.php
<html>
<h1><?= htmlspecialchars($title)?></h1>
<p>
<?php if (date('H') >= 12): ?>
    午後です
<?php else: ?>
    午前です
<?php endif ?>
</p>
</html>

一方で、現在のPHPファイルの大部分は以下のようなクラス定義や関数定義だけが置かれているものが大半です。

<?php

declare(strict_types=1);

namespace App;

class Foo
{
    // ...
}

HTMLが混在することはないのに <?php から始まるのは、およそ痕跡的なものだと言っていいでしょう。

字句解析上での <?php ?> の扱い

字句解析(lexical analysis)とは構文解析の1ステップです。次のようなソースコードを字句解析することを考えてみましょう。

if ($a == true) { echo "foo bar"; }

このようなソースコードの文字列を['if', '(', '$a', '==', 'true', ')', '{', 'echo', '"foo bar"', ';', '}']のような配列に分解する作業が字句解析です。

PHPコードの標準関数にはtoken_get_all()関数があります。この関数は文字列を分解するだけでなく、どのような種類の文字の塊なのかの意味付け(トークナイズ)までをセットで行なってくれます。

token_get_all()の結果を読みやすく可視化すると次のようになります。

atama<?php echo "karada"; ?>oshiri
[
    {
        "T_INLINE_HTML": "atama"
    },
    {
        "T_OPEN_TAG": "<?php "
    },
    {
        "T_ECHO": "echo"
    },
    {
        "T_WHITESPACE": " "
    },
    {
        "T_CONSTANT_ENCAPSED_STRING": "\"karada\""
    },
    {
        "": ";"
    },
    {
        "T_WHITESPACE": " "
    },
    {
        "T_CLOSE_TAG": "?>"
    },
    {
        "T_INLINE_HTML": "oshiri"
    }
]

気になる方は以下のページでいろいろ弄れます。

PHPはHello worldと書くだけでHello worldが出力できるプログラミング言語として知られていますが、そのソースコードを字句解析するとこうなります。

Hello, world!
hello.php
[
    {
        "T_INLINE_HTML": "Hello, world!"
    }
]

オペコードコンパイル後の <?php ?> の扱い

バイトコード(bytecode)とは、PHPの構文で書かれたソースコードを解釈して、よりシンプルな形式の命令列として表現する中間表現のことです。PHPの文脈ではオペコード(opcode)と呼ばれますが、同じ意味です。

先ほどのコードを改めてバイトコード(オペコード)コンパイルしてみましょう。

atama<?php echo "karada"; ?>oshiri

オペコードコンパイルされたPHPコードを目視確認する方法はいくつかありますが、Webでは3v4lの機能を使ってVLDの出力を表示するのが楽です。

Finding entry points
Branch analysis from position: 0
1 jumps found. (Code = 62) Position 1 = -2
filename:       /in/2sPj0
function name:  (null)
number of ops:  4
compiled vars:  none
line      #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
    1     0  E >   ECHO                                                     'atama'
          1        ECHO                                                     'karada'
          2        ECHO                                                     'oshiri'
          3      > RETURN                                                   1

opというのが命令、operandsというのが引数のことです。

つまり、<?php ... ?>の外側に書いたコードは、その内容を文字列としてechoに渡すのと同じで、以下のPHPコードと等価ということになります。

<?php
echo 'atama';
echo 'karada';
echo 'oshiri';

<?php ... ?>タグの何が問題なのか

玄関で抜いだ靴はきちんと揃えないと気が済まない几帳面な性格の方にとっては非常に不満があることは承知していますが、現代のPHPコードでは <?php を書いても ?> は書かないのが基本的な慣習になっています。

ここまで説明したように <?php ... ?> の外側は echo と等価だというのがPHPの構文および実行時の基本的な振る舞いなので、何か出力する必要がなければ?>で終端することによって得られるメリットは特にありません。

また、<?phpの前や?>の後にうっかり何か文字を入力されると意図しないechoが発生することになります。PHPでの意図しないecho出力はHTTPレスポンスに影響しますので、これによって適切なHTTPヘッダを返せないこともあり動作を壊します。

これを防ぐには、PHPUnitでbeStrictAboutOutputDuringTests="true"の設定をしてあればテスト対象の実行時に意図しない出力があったときに警告を出したりPHPStanのルールでチェックすることもできますが… 経験則では、そういった仕組みが用意されていないプロジェクトに限って ?> を付けることに拘っているという 偏見 経験則があります。

意図しない出力の害

意図しないスペースが出力されちゃっても」表示崩れなんてそうそう起こらないんじゃないの?」と考える方もいらっしゃるかもしれませんが、大きく分けると二つの害があります。

  1. HTTPヘッダが出力できない
  2. namespacedeclare(strict_types=1);が追加できない

1について、HTTPメッセージはヘッダとボディに分かれているので、何かをechoすると、その場でヘッダを吐き出してボディの出力を始めてしまいます。その後にheader()関数やsetcookie()関数を呼ぶと期待通りに機能しません。

2については… 説明するのがめんどくさいので以下の記事を読んでください。

PHPから<?phpタグを消そうとする試み

現代的なPHPプロジェクトでは、ほとんどのファイルではPHPの処理やクラス定義とHTMLテンプレートを混在させることはないので、<?phpは生存の役に立たない盲腸のようなものだと考えることもできます。

そのため、<?phpがなくてもコードを書けるようとしようという提案は何度か行なわれています。

moriyoshiによる提案(四月馬鹿)

PHP開発者の小泉守義さんが:cherry_blossom:2012年4月1日:cherry_blossom:に起草した提案です。

本文のアーカイブは現存していないのですが、手の込んだことにパッチが用意されています。

moriyoshiさんは4月1日中に提案本文を削除しており、その意図するところは明らかでしょう……。

moriyoshiさんが提案して実装された機能としてはビルトインサーバーがあります。

Boutellによる提案 (Source Files Without Opening Tag)

2012年4月8日にThomas Boutellが起草した提案です。

ざっくり2種類の案を提示しています。

  1. ファイルロード時にフラグを明示させる
    • require 'file.php'; 従来通りのHTMLが混在されてる(かもしれない)コード
    • require 'file.php' AS INCLUDE_PURE_CODE; HTMLが含まれない純粋なPHPファイル
  2. ファイル名の拡張子で区別する
    • file.php ← 従来通りのHTMLが混在されてる(かもしれない)コード
    • file.phpp ← HTMLが含まれない純粋なPHPファイル

I have abandoned this proposal. I have come to feel it changes the spirit of PHP too much, offering too little gain for the degree of unhappiness it inspires and the potential for confusion it creates. I am leaving it here for historical purposes. -Tom Boutell

私はこの提案を断念しました。この提案は PHP の精神を大きく変え、不幸の度合いや混乱の可能性に比べて得られるものが少なすぎると感じたからです。歴史的目的のためにここに残しておきます。 -Tom Boutell

ということで断念されています。

提案自体でもオートローダーは考慮されていますが、現代ではほとんど全てのPHPコードはComposer経由でオートロードされるものになってしまったので、使いどころがあまりない提案になってしまっているという個人的な雑感があります。

Craigによる提案 (New File Type for Pure-Code PHP Scripts)

上記の議論にも参加していたKris Craigが2012年4月12日に起草した提案です。

Boutell提案(2)の拡張子を切り出したような内容ですが、.php .phpp .phpfの3種類の拡張子に分けることを提案しています。

先のBoutell提案にも言えることですが、パッチもなく実現可能性があんまりなくて、何が嬉しいのかがいまいち伝わってこない、要領を得ない提案という感じがします。

2010年にロールバックされたはずのPHP 6がターゲットなのは現代人にとっては???という感じなのですが、これは仕方のないことで、PHP 6.0をスキップして時期メジャーバージョンを7.0にする方針が定まったのは2014年です。

yohgakiによる提案 (Optional PHP tags by php.ini and CLI options)

2012年4月9日に大垣靖男さん(yohgaki)がmoriyoshi提案を復活させる形で復活させたものです。

要約すると、この提案で提案しているのは以下の2点です:

  1. template_mode=offの導入
    • php.ini, 環境変数, コマンドラインオプションで制御可能
    • 冒頭の <?php のみ互換性のために許可
    • 途中の <?php はパースエラー
    • 最後の ?> 以降は互換性ために無視する
  2. template_modeの影響を受けない script / script_once() 言語構造の導入

この提案で主張されているのはLFI攻撃(Local File Inclusion)への耐性強化です。パッチが添付されているところが上記の2つと違うところですが… これはmoriyoshiさんのパッチをそのまま載せているだけで、提案された機能は特に実装されていません。

これも私見にはなりますが… 「既存コードと完全に互換性がある」ということを謳っているものの、実行時設定やロード方法によってパース結果が異なるというところ、物凄く不穏な気配があります……。
フレームワークでLFI攻撃がいくつも報告されているということが挙げられているものの、PHP: include - Manualで警告されているような筋の悪いファイル読み込みが原因であり、この提案による緩和効果は著しく限定的に思えます。

こちらの提案も建設的な議論は進まず、Rasmus Lerdorf(PHPの原作者)からもここで防止しようとしているずさんなコーディングの問題よりも、おそらくより深刻なセキュリティ問題を引き起こすと言われてしまいました。私もそう思います。

yohgakiによる提案2 (Introduce script only include/require)

前から時を経て、2015年2月22日に大垣靖男さん(yohgaki)が再提出した提案です。これは直接<?phpを省略する機能を提案するものではないのですが、一連の流れの中にあるので紹介しましょう。

この提案はini_set('zend.script_extensions', '.php .phtml .inc .module .etc');という実行時設定を追加し、指定された拡張子のファイルだけをinclude/requireできるようにしようというものです。

ここで問題提起しているのは、PHPのinclude/requireが他の言語の構文と比べて動的で、LFI攻撃に脆弱な傾向があるということがあります。

python
# モジュール検索パスを走査して foo モジュールをインポートする
import foo
ruby
# ライブラリ探索パスを走査して foo.rb を読み込む
require 'foo'

Pythonではモジュールの動的インポートにはimportlibという普通に使わない機能を使う必要があり、RubyのKernel.#requireも絶対パスを指定することは可能ですが有効な拡張子が*.rbと動的モジュール(*.soなど、OS依存)に限られています。

一方でPHPはどんなファイルでも指定することが原理的に可能で、ファイル内に<?phpを書かなければファイルの中身が全て漏洩してしまうので危険だという指摘それ自体は正しいのですが…

そもそも外部入力を引数にしてファイルを動的ロードしないでください。そのような見る人が見れば一瞬で「匂う」コードでもフールプルーフにしたいという思想自体はわかりますが、この箇所を改善することで全体的な安全性はそれほど向上しないという意見は妥当だと思います。

この提案は実際に投票まで進んだ上で否決されています。

Hack

ところで、Meta社(Facebook)が開発しているHHVM(HipHop Virtual Machine)という言語処理系と、Hackというプログラミング言語をご存じでしょうか……? もはや旧聞に属する話題なのですが、かつてこのHHVMはPHPの互換処理系として開発され、HackもPHPを発展させた言語として開発されていました。

Hackは2013年に公開された言語ですが、既存PHPコードの冒頭の<?php<?hhに置き換えることでHackとして実行できるようになっていました (過去形)。基本的にはHackはPHPのスーパーセットだった(当時)のですが、この言語では ?> で閉じて出力するようなことはできないようになっています。(つまり、LFI攻撃耐性があります)

また、2019年2月リリースのHHVM 4.0.0からはPHPサポートを終了してHack専用の言語処理系になったほか、拡張子を.hackにするとファイル冒頭の<?hh開始タグが不要になりました。つまりは拡張子によって挙動を変えるBoutell案やCraig案に近い実装です。

銀河戦争P++

これも2019年のことなのですが、P++ という「野心的な」厳格バージョンのPHPを開発しようというアイディアが発表されたことがありました。

この事件の契機になったのは <? ... ?> というショートタグを廃止しようとしたことによる議論でした。このタグは設定によって無効化されるので、特にライブラリコードなどは、必ず <?php<?= だけを使用しなければいけません。

これを廃止して「構文解析処理をシンプルにしたい」vs「既存コードに手を加えて<?<?phpに変換させる必要があるのか」という大議論です。

事件の顛末について気になる方は上記の記事を読んでいただきたいのですが、「壊れていないものを直す必要があるのか」「極端に筋が悪いコードの問題を塞ぐために全利用者の実行環境に影響を及ぼす必要があるのか」というのは持つべき非常に重要な観点かと思います。

まとめ

後半は非常に話がぶれてしまった感がありますが、結局のところは冒頭でまとめた通りです。

  • <?phpが必要なのはHTMLテンプレートが出自であるゆえの歴史的経緯である
  • <?phpを取り除くのは技術的には可能だが、懸念事項も多く簡単ではない
    • Hackは実現したので、誰かが責任を持って取り組めば可能

ここまでの提案は正直あまり冷静な議論の結果で決まったものはほとんどないので、必要だと思った人が説得力のある適切な議論ができたら取り入れられる可能性はなくもないのではないかと思います。

個人的には.hackみたいに専用の拡張子を用意するのが推しですが、そもそも現状のPHPファイル自体が.phpという拡張子にあまり依存していないので、それを覆す攻め手を考える必要がありそうです。私はやらないので誰か頑張って。
PHP 12.0くらいの目玉機能になったらおもしろいかもしれませんね。

124
47
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
124
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?