クラスファイルのオートローダーを実装する | A Day In The Boy's Life

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

システムの規模が大きくなってくるといろんなクラスファイルが出来上がり、必要なものをincludeやrequireするのがかなり面倒になったりプログラムの可読性が悪くなったりします。

こういうときにオートローダーがあれば便利なわけですが、PHP5からは標準でクラスのオートロードの機能が実装されています。



PHPでクラスのオートローダーを作る


PHP5からは、__autoload() という関数があるのですが、現在推奨されておらず変わりにspl_autoload_register() 関数を用いることが推奨されています。


今回は、オートローダー機能がついた基底クラスを作り、サブクラスでは特に意識せずに所定のライブラリを利用できるような構成のプログラムを書いてみます。


<?php

class baseClass {

    function __construct() {
        define ('LIB_DIRPATH', '/path/to/lib/');
        spl_autoload_register(array($this, 'classAutoLoad'));
    }

    /**
     * オートローダー
     * @param string $className : ロードするクラス名
     */
    public function classAutoLoad($className) {
        $path = LIB_DIRPATH . $className . '.php';
        if (is_readable($path)) {
            require_once $path;
        }
    }
}

spl_autoload_register()関数によって、不明なクラスオブジェクトを生成しようとした際に呼び出す関数を定義しておきます(ここではclassAutoLoad()関数)。

コンストラクタとして定義しているので、サブクラス側では意識せずに自動的に呼び出されます。


classAutoLoad()関数は、呼び出したクラス名が自動的に引数として渡されるので、それをライブラリが置かれているパスの中からrequireするようなロジックを組み立てておきます。

クラス名とそれを定義しているプログラム名は同名になるようにしておきます。


利用する方は下記のような書き方になります。


<?php

require_once 'baseClass.class.php';

class subClass extends baseClass {
    function main() {
        $class = new fooClass();
        $class->checkString("xyz"));
    }
}

$obj = new subClass();
$obj->main();


subClass側のmain関数で呼び出しているfooClassは特にincludeやrequireはしていませんが、基底クラスのほうで定義しているclassAutoLoad()によって自動的にrequireされます。
これで、多くのクラスファイルを利用するプログラムでも先頭に長大なrequireを書く必要はありません。


spl_autoload_register()は、クラスをオートロードする役目というよりは認識できないクラスオブジェクトが生成されたときの処理を定義するという感じで、結局はその時にクラスファイルを読み込むためのロジックをclassAutoLoad()で書いておくということをしています。


名前空間に対応させたい場合、classAutoLoad()に渡される引数に名前空間付のクラス名が渡されるので一工夫が必要です。

また、ライブラリのパスがphp.iniに定義されているinclude_path内に必ずあるというならstream_resolve_include_path() を使ったほうが便利かもしれません。


    /**
     * オートローダー
     * @param string $className : ロードするクラス名
     */
    public function classAutoLoad($className) {
        // 名前空間付の場合は分解してクラス名だけを取り出す
        $className = (array_pop(explode('\\', $className)));
        // php.iniのinclude_pathからファイルを探してパスを返してくれる
        $path = stream_resolve_include_path($className . ".php");
        if ($path !== FALSE && is_readable($path)) {
            require_once $path;
        }
    }


呼び出し側は、下記のように名前空間付で呼び出しても勝手にロードしてくれます。


    function main() {
        $logic = new \hoge\FooBar();
        var_dump($logic->checkString("abc"));
    }


名前空間の扱いは自動で分解するなど、もっとうまいことやってくれたらいいのになぁとか思ったりはしますが。