読者です 読者をやめる 読者になる 読者になる

MANA-DOT

PIXEL ART, PROGRAMING, ETC.

PHP Badparts 4.catch節(というよりタイプヒンティング)

PHP PHP bad parts プログラミング

いきなりですが問題。次のコードを実行するとどんな出力がされるでしょうか。 (前提知識として、DateTimeクラスのコンストラクタに不正な値を渡すと、Exceptionが投げられます。)

<?php
namespace Hoge;

try {
        new \DateTime("buggg");
} catch (Exception $e) {
        echo 'error!!';
}

正解は以下のとおり。

PHP Fatal error:  Uncaught exception 'Exception' with message 'DateTime::__construct(): Failed to parse time string (buggg) at position 0 (b): The timezone could not be found in the database' in /home/mana/catch.php:5
Stack trace:
#0 /home/mana/catch.php(5): DateTime->__construct('buggg')
#1 {main}
  thrown in /home/mana/catch.php on line 5

Exceptionという例外がcatchされてないよ!というよくあるヤツです。 あれれ、でも僕はちゃんとExceptionをcatchするようにコードを書いたのに!

名前空間内なので、Exceptionと\Exceptionは別物

実はcatchされないのは当然で、名前空間Hogeの中で記述したコードだからです。 単にExceptionと書いた場合、Hoge\Exceptionのことだと処理系は思うわけですね。 以下のようにしてあげればちゃんとcatchすることができます。

<?php
namespace Hoge;

try {
        new \DateTime("buggg");
} catch (\Exception $e) {
        echo 'error!!';
}

存在しないクラス名の参照を参照しているけど・・・

何だ問題無いじゃんと思うかもしれませんが、ちょっと考えてみてください。 たしかに、名前空間内なので単にExceptionとかくとHoge\Exceptionと解釈され、Exceptionクラスとはマッチしないのでcatchされない、は正しい挙動です。

ですが、Hoge\ExceptionExceptionが違うクラスなのはいいのですが、そもそも存在しないHoge\Exceptionをコード中に参照しているにもかかわらず、そのことでclass not foundなどのエラーを出さなくていいのでしょうか。

どうやらタイプヒンティングの仕様らしい

いろいろ試してみたところ、catch節以外でも関数定義のタイプヒンティングでも、存在しないクラス名でも特に怒られずに実行できます。

<?php
function test(Hogehogehoge $hoge=null) {
        echo 'hoge!';
}
test();

もちろん、存在しないクラス名なので、上の関数を引数ありで実行することは絶対にできませんが。

仕様と言われればそれまでだけど・・・

例えば、上のように\ExceptionとすべきところをExceptionと書いてしまうことは十分考えられますし、 名前空間を使用していなくてもExceptonのようにタイポしてしまうことはコードを書くのは人間ですので有り得る話です。

このようなミスを犯した場合、コードをじっくり読んで気づく以外には実行結果を観測することでしかミスに気づくことはできません。 関数に関しては引数有りで実行すればすぐ気づけるので問題になることはまずないでしょうが、 たちの悪いことにcatch節はしばしば殆ど通らない場合もあるため、中々ミスに気づくことができないかもしれません。

もし、処理系が存在しないクラス名のcatch節を通った時にclass not foundエラーを出してくれれば、その時点で気づくことができるのですが、 おそらくrequireなどで動的にクラスが存在したりしなかったりするので、厳しく存在判定をしてしまうわけにも行かないのでしょう。 (僕個人としては、そもそも見えてないクラスでcatchできるようにするなよ、と思うわけですが。ヒンティングがある時点でそのコードはクラスに依存しているので。)

とりあえず考えうる対策

といっても、エラーケースも含めてすべてのパターンをしっかりテストすることしか思いつきません。 想定する例外が発生されるケースで正しいエラー処理が行われているか、すべてのケースでテストするべきでしょう。

例外処理のテストをサボっているテストもしばしばありますが、こういう事情があるとそういうわけには行きません。 特に、最初の例のように名前空間のつけ忘れがはまりやすいと思われるので、十分に注意し、しっかりテストを書くようにすべきでしょう。