All Articles

直前にrequireで読み込んだクラスファイルに定義されたクラスを取得するちょっとしたアイデア

オートロード機構の所で悩んでたんですが、多分これでうまくいくかなというメモ。

フレームワークとかで困った所

大体最近のFWにはクラスのオートロード機構が備わってますが、その内部では、spl_autoload_register(__autoload()でもいいですが)に登録された関数でrequireするのが定石です。で、namespaceは別として、require(_once)で読み込まれたファイルに実際に特定のクラスが存在しているかどうかをチェックする方法に悩んでました。

今まではこんな感じで存在チェックしてました:

$class = 'Hoge';
require_once('/path/to/' . $class . '.php');
if ( ! class_exists($class, FALSE) ) {
  throw new RuntimeException('うんたらかんたら');
}

return new $class();

他にいい方法はないものか

上記だと、フレームワームのnamespaceとかでプレフィックスがついていた場合、例えば、ファイルはHoge.phpだけど中で定義するクラスはAPP_Hogeだったりする場合(それが本当にいいのかは置いときまして)、本当にそのクラスが定義されているかどうかをチェックする方法ってないのかなーって考えてました。で、暫定で下のコード。

$prefix = 'APP_'; // FWのどっかで定数で定義されてる
$class = 'Hoge';
require_once('/path/to/' . $class . '.php');
if ( ! class_exists($class, FALSE) ) {
  // プレフィックスつきも検索
  $class = $prefix . $class;
  if ( ! class_exists($class, FALSE) ) {
    throw new RuntimeException('うんたらかんたら');
  }
}

return new $class();

これでも良いんですが、アプリケーションを実装する人によってはプレフィックス有り/無しは自由であって欲しい。そして自分の中では、「プレフィックス付き = コアクラス拡張」、「プレフィックス無し = オーバーライド」という位置づけを目指してました。なのでもうちょっと確実に定義されたクラスって取れないかなーって考えたのが以下です。

$class = 'Hoge';
require_once('/path/to/' . $class . '.php');
$declared = end(get_declared_classes());
if ( ! preg_match('/.+' . $class . '$/', $declared) ) {
   throw new RuntimeException('うんたらかんたら');
}

return new $declared();

これならクラスの存在は少なくとも保証されているので、ちょっといい感じかな、と。プレフィックスの有り/無しもあまり気にする事もありませんね。継承関係があり、基底クラスのオートロードが走った場合でも、クラスの宣言順はHogeクラスが最後になるので大丈夫でした。理屈的には、

「extendsキーワードがあれば、先に基底クラスのクラスチェック(なければオートロード走る)を行い、続いて継承を行い、クラス宣言を完了とする」

という動きのようなので、継承関係があってもgetdeclaredclasses()の最後のインデックスにはHogeが入りますね。

ただし

当たり前ですが、1ファイル中に複数のクラス宣言があった場合は、一番最後のクラス宣言を取得するので、「1ファイル1クラス」というルールが成立する上での手法ですね(というか大体そういうルールですよね?ね?)