クラスのオートローディング : 特殊関数 __autoload() / 静的遅延束縛 late-static-bindings

PHP: クラスのオートローディング - Manualによると…未定義のクラスをコールしたときに起動する。これを使うことによりincludeなどを使って外部ファイルからクラスを読み込む手間が省ける。ということらしい。

void __autoload( string $class_name )

ということでコードを書いてみる

以下のようなファイルを /my_class/test_class.php に用意する

<?php
class TestClass{}

/* End of file test_class.php */

で、下のコードを実行すると…

<?php
function __autoload($class_name)
{
  if($class_name == 'UndefinedClass'){
    class UndefinedClass
    {
      public $var = "hello";
    }
  }  else {
    $filepath = preg_replace('/([A-Z][a-z0-9]+)/u', '_$1_', $class_name  );
    $filepath = str_replace('__', '_', trim($filepath, '_'));
    $filepath = '/my_class/' . strtolower(basename($filepath)) . '.php';
    try {
      if( ! include $filepath ){
        throw new Exception("ファイルがないっぽい");
      }
    } catch(Exception $e) {
      echo $e->getMessage();
    }
  }
}

$obj = new UndefinedClass;
echo $obj->var;
$obj = new TestClass;
var_dump($obj);
new Nonexistent;
?>

結果

hello
object(TestClass)[2]
Warning: include(/my_class/nonexistent.php) [function.include]:
 failed to open stream: No such file or directory in *** on line 16
ファイルがないっぽい
Fatal error: Class 'Nonexistent' not found in *** on line 29

Warning の出力を抑制するために エラー制御演算子 @ でも付け加えておくべきかな? オートローディングしても見つからなかった場合は強制終了なので、Fatalエラーを出す前に catch{ die($e->getMassage())) } して終了させてしまうほうがいい。

<?php
function __autoload($class_name){
  echo $class_name;
}
__autoload("テスト\n");

$class= "/../ディレクトリトラバーサル";
@new $class;
?>

結果

テスト
/../ディレクトリトラバーサル

普通に呼び出せるのかなとコールしたらできた。

オートロードっていう名前だからといってロードしなくちゃいけないわけでもなく中身は何でもよさそう。

インスタンス生成に変数を利用するとパストラバーサルの危険性がある。マニュアルに書いてあったのはこいつのことか。。

そんでもって new 演算子って マルチバイトが普通に通るんだな…

静的遅延束縛 Late-Static-Bindings

PHP5.3.0~の機能。PHP: 遅延静的束縛 (Late Static Bindings) - Manual。参考:遅延静的束縛と forward_static_call() - 親方、空から覚え書きが!

<?php
class Z
{
  public static function p(){
    echo __METHOD__ . " / " . get_called_class() . "\n";
  }
}
class A extends Z
{
  public static function p(){
    echo __METHOD__ . " / " . get_called_class() . "\n";
  }
  public static function call_self(){
    self::p();
  }
  public static function call_parent(){
    parent::p();
  }
  public static function call_lsb(){
    static::p();
  }
  public static function call_a(){
    A::p();
  }
  public static function call_a_lsb(){
    forward_static_call(array('A', 'p'), NULL);
  }
}
class B extends A{}
class C extends B
{
  public static function p(){
    echo __METHOD__ . " / " . get_called_class() . "\n";
  }
}
C::call_self();
C::call_parent();
C::call_lsb();
C::call_a();
C::call_a_lsb();
?>

結果

A::p / C
Z::p / C
C::p / C
A::p / A
A::p / C

うーん…なんだか理解ができない…。遅延静的束縛により呼び出し元の情報を保持しつつ他のメソッドを呼び出せるということ?静的遅延束縛をつかうときは static:: や foward_static_call() などを使う…ってことか。なんだか分からないけど分かったような気になっておこう。理解できるまで復習しつづけよう…

ちょっと整理しよう…

<?php
class Z
{
  public function func1()
  {
    echo __METHOD__ .' called by '.get_called_class()."\n";
  }
  public function func2()
  {
    self::func1();
    $this->func1();
    static::func1();
  }
}
class A extends Z
{
  public function func1()
  {
    echo __METHOD__ .' called by '.get_called_class()."\n";
  }
}
$z = new Z;
$a = new A;
$z->func2();
$a->func2();

結果

Z::func1 called by Z
Z::func1 called by Z
Z::func1 called by Z
Z::func1 called by A
A::func1 called by A
A::func1 called by A

self:: はそのスコープ、static:: は呼び出し元。継承が絡まなければ self:: も static:: も厳密には違うけれど実質的な動作に違いはない。

<?php
class Z
{
  private function func1()
  {
    echo __METHOD__ .' called by '.get_called_class()."\n";
  }
  public function func2()
  {
    self::func1();
    $this->func1();
    static::func1();
  }
}
class A extends Z
{
  private function func1()
  {
    echo __METHOD__ .' called by '.get_called_class()."\n";
  }
}
$z = new Z;
$a = new A;
$z->func2();
$a->func2();

結果

Z::func1 called by Z
Z::func1 called by Z
Z::func1 called by Z
Z::func1 called by A
Z::func1 called by A

Fatal error: Call to private method A::func1() from context 'Z' in *** on line 12

オーバーロードしたprivateメソッドにアクセスする場合は…可視性によるアクセスの優先度を理解していれば意味は分かるはず。

Share
関連記事