グローバルに展開してしまう
defineを用いて定義した定数は、定義したあと実行するコードのどの箇所からでも参照できます。 モダンプログラミングでよく言われますが、グローバル空間にいろいろなものを定義しまくるのはあまり行儀がよいものではありません。
定数なので、勝手に何処かで値を変更されるという事にはならないのでまだ良いのですが、 それでも常に定義するときに衝突を考慮しなければいけないこと、衝突を避けるためには必然的に長い名前になり、アクセシビリティが悪くなるということになります。
また、PHPの組み込み関数群がそれらが引数として用いるための定数を大量にグローバルにぶちまけているのも問題です。
存在しない定数にもアクセスできる
例えば以下のワンライナーを実行すると、
php -r "var_dump(NOT_EXIST_CONSTANT);"
以下の様な結果になります。
PHP Notice: Use of undefined constant NOT_EXIST_CONSTANT - assumed 'NOT_EXIST_CONSTANT' in Command line code on line 1 string(9) "NOT_EXIST_CONSTANT"
一応Noticeは出るのですが、出るだけで普通に文字列として扱われてしまいます。 これは、変数名の$つけ忘れや定数のタイポなどがそのまま動いてしまうということを意味しています。
$foo=1; var_dump(foo); // Noticeは出るが、string(3) "foo"
Noticeを出力するようにしていれば気づきますが、そういう問題じゃなく、 万が一にもNoticeを出力しない環境だと、気づかないままリリースしてしまう可能性もあります。
PHPのこの挙動は人間がミスに100%気づいてそれを修正できることを暗黙の前提としたあまりに危険な挙動であり、 人間がミスをする前提で設計されたプログラミング言語のほうが、バグは作り込みにくいでしょう。
defineは関数である
PHPの定数を作るdefineは関数であり、引数として変数をとることができます。すなわち、
if ( $foo ) { define($bar, $baz); }
といったコードを書けます。実行時によってあったりなかったり、名前や値がすこしずつ違ったりといった定数と呼べるのか定かではない定数を作れるということです。 もちろん、こんなコードを書こうと考える人はそういないでしょうが、書けるというのも事実です。
このような定数の定義が万が一あったなら、先ほどの問題と合わせて非常にバグを生みやすい状態であるといえるでしょう。
対策
以上のように、いろいろ怖すぎるPHPの定数。対策はこんな感じでしょうか。
- できるだけ定数を使わず、クラスのconstなどを利用する。
- どうしても使う場合は、しっかりCIでコードチェックを行い、変数名タイポなどは自動的に判別できる環境を作る。
- defineの引数はなるべく即値とする。
定数を全く使わなかったとしても、変数名の$のつけ忘れなどで予期せぬ挙動を起こすPHPの定数の仕様。あまりに罪深すぎるでしょう。