5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PHPでは未定義の定数は文字列として処理される

Last updated at Posted at 2020-01-04

要約

  • PHP では 未定義の定数文字列 として処理続行する
  • Warning を無視するべからず
  • ついに PHP 8 からは動かなくなる
  • みんな PHPStan を使おう!

問題

突然ですが、PHP でこう書いたとき、どのように動くでしょうか?

echo date(Ymd);

これを書いた人は、おそらく date("Ymd") と書きたかったのでしょう。

うん、 気持ちは 解る。

一見すると、ダブルクォーテーションが前後にないので、エラー終了しそうです。

ところが、実際には 警告は出力されるが処理続行します。

PHP Warning:  Use of undefined constant Ymd - assumed 'Ymd' (this will throw an Error in a future version of PHP) in test.php on line 3
20191231

理由

未定義の定数?

処理結果を見てみると、 Use of undefined constant Ymd (未定義の 定数 Ymd の使用)と書いてあります。

どうやら、PHP は Ymd定数 として認識しているようです。

定数というと「全て大文字のアルファベット」という慣習に親しみすぎて、それ以外は定数でないかのように思ってしまいますが、定数名の定義は以下のとおりです( PHP マニュアル )。

有効な定数の名前は、 文字またはアンダースコアで始まり、任意の数の文字、数字、 アンダースコアが後に続きます。

なるほど。たしかに Ymd は定数ですね。

なんで処理続行したの?

しかし、先程のコードは、定数 Ymd が未定義であるにもかかわらず、処理続行しています。

試しに、こんな風に直接 var_dump() してみましょうか。

var_dump(Ymd);
// --> string(3) "Ymd"

未定義の定数なのに 文字列 として処理されている!

こんな仕様あるの?

はい。ちゃんと マニュアル にも書いてありました。

未定義の定数を使用した場合、ちょうどstringとして コールしたかのように(CONSTANT vs "CONSTANT")、 PHPはその定数自体の名前を使用したと仮定します。 このような振る舞いは、PHP 7.2.0 以降では非推奨となり、E_WARNING が発生するようになりました。 (それより前のバージョンでは、 E_NOTICE が発生していました)

ここで重要なのは、E_NOTICE だろうが E_WARNING だろうが 処理は続行する ということです。

問題2

先程は、たまたま期待どおり動いていました。試しに、こう書いてみたら、どうでしょうか? ログファイルに現在時刻を書き込んでいくだけの簡単なプログラムです。

define("ERROR_LOG_FILE", "error.log");

// 定数名間違えちゃった!
$fp = fopen(EROR_LOG_FILE, "a");
fwrite($fp, date("Ymd H:i:s") . PHP_EOL);
fclose($fp);

これを実行すると、ディレクトリの中に EROR_LOG_FILE というファイルが作られます。

だいたい、こういうのは単体テストで見つかるのですが、1回、そのままリリースしてしまったことがあってですね・・・1

テスト担当者は なんかたくさん Warning が出てるなー ぐらいにしか思わなかったらしく・・・

たかが Warning、されど Warning 。Warning を無視するべからず。

PHPStan

最近の IDE は優秀なので、未定義の定数を書くと、未定義だよと教えてくれます。
VSCode だと、定数の下に赤い波線を出してくれたり。
でも、それはまだまだ人力に頼ってしまっていると思います。もっと機械的にできないものでしょうか。

そこで、 静的解析の出番です!!!(ドンドン)

例えば、冒頭のコードを PHPStan で静的解析させてみましょう。

echo date(Ymd);

静的解析すると、以下のようにエラーが表示されます(実際の解析結果は こちら )。

+--------------------------------+
| Line | test.php                |
+--------------------------------+
| 3    | Constant Ymd not found. |
+--------------------------------+

こうして地上に平和がもたらされたわけです。IN TERRA PAX!

今後

PHP 7.2 から、Warning に this will throw an Error in a future version of PHP と表示されるようになりました。
そして、ついに、PHP 8 では、もはや処理続行を許さず、 Error として例外が送出されるようになります。

ちなみに、この件を扱っている RFC (PHP RFC: Deprecate and Remove Bareword (Unquoted) Strings)を見ると、以下のようなコードが載っています。

$foo = flase; // typo!
// ...
if ( $foo ) {
   var_dump($foo); // string(5) "flase"
}

$foofalse ならば、 if 文の中のコードは実行されないはずです。なのに flase とタイプミスしたせいで真逆の結果になるという・・・。おそろしい・・・。

RFC によると、この奇妙な動作は、1998 年にリリースされた PHP 3.0 で導入されたそうです。どうして PHP 3.0 で導入されたのか?

The old source code also includes the documentation with which PHP 3 shipped, which seem to have no mention of this behaviour, and no examples which take advantage of it.

古いソースコードには、そのときのドキュメントも含まれている。だけど、この動作については何も言及されていないし、それを使った例も記載されていないようだ。

何も言えねえ・・・

こうして、約 20 年間、数多の開発者が陥ってきた落とし穴は、全会一致(41 vs 0)で廃止されることになりました。南無〜

余談

PHPStan で以下のコードを静的解析してみました(実際の解析結果は こちら )。

define("ERROR_LOG_FILE", "error.log");

// 定数名間違えちゃった!
$fp = fopen(EROR_LOG_FILE, "a");
fwrite($fp, date("Ymd H:i:s") . PHP_EOL);
fclose($fp);

すると、このように出力されたのですが、6行目と7行目がどうしてエラーになるのかが分からないです。
$fp は明らかに resource 型だと思うんですけどねぇ・・・

+------------------------------------------------------------------------------------+
| Line | test.php                                                                    |
+------------------------------------------------------------------------------------+
| 5    | Constant EROR_LOG_FILE not found.                                           |
| 6    | Parameter #1 $fp of function fwrite expects resource, resource|false given. |
| 7    | Parameter #1 $fp of function fclose expects resource, resource|false given. |
+------------------------------------------------------------------------------------+
  1. 「テストコード書いてないの?」とツッコミがありそうなので、先に書いておきます。令和のこの時代にPHP5.3を使っているような現場で、テストコードを書くという基本的人権が保証されているわけないじゃないですか・・・

5
6
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
5
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?