autoloadの実装に少し迷ったので、Symofnyの実装を見てみた。
また、マニュアルを参考にして小さなレベルで使用できるautoloadを書いてみた。
以下、メモ。
Symfony 1.0
実務で使用しているのが1.0系なので、まずそれを。/vender/lib/util/sfCore.class.php(抜粋)
static public function splAutoload($class)
{
// load the list of autoload classes
if (!self::$classes)
{
$file = sfConfigCache::getInstance()->checkConfig(sfConfig::get('sf_app_config_dir_name').'/autoload.yml');
self::$classes = include($file);
}
// class already exists
if (class_exists($class, false))
{
return true;
}
// we have a class path, let's include it
if (isset(self::$classes[$class]))
{
require(self::$classes[$class]);
return true;
}
// see if the file exists in the current module lib directory
// must be in a module context
if (sfContext::hasInstance() && ($module = sfContext::getInstance()->getModuleName()) && isset(self::$classes[$module.'/'.$class]))
{
require(self::$classes[$module.'/'.$class]);
return true;
}
return false;
}
手順は以下の通り。
- autoload.ymlから生成されたクラスリストファイルを読み込んでself::$classesに格納
- (class_exists($class, false))でチェック。既に呼び出されていれば終了(trueを返す)。第2引数にfalseをセットすることで__autoloadを呼ばないようにする。
- 指定したクラスがクラスリストにあればrequire(trueを返す)。
- なければモジュールクラスを検索しあればrequire(trueを返す)。
- 読み込みに失敗したらfalseを返す。
クラスリストファイルは、sfAutoloadConfigHandlerクラスによって作成される。設定ファイルをパース(YAML→連想配列)したあと、recursiveなどの条件を見つつ検索して列記するっぽい。いわば、静的なautoloadと言うべきか。自動生成という感じは余りしない。ていうか、設定をキャッシュするフレームワークならでは。
あとrequire_onceは使わずに、__autoloadを呼ばない形でclass_existsを使っているようだ。
Symfony 1.3/1.4
現行の1.3系/1.4系のソースも眺めてみた。/lib/autoload/sfAutoload.class.php(抜粋)
public function loadClass($class)
{
$class = strtolower($class);
// class already exists
if (class_exists($class, false) || interface_exists($class, false))
{
return true;
}
// we have a class path, let's include it
if (isset($this->classes[$class]))
{
try
{
require $this->classes[$class];
}
catch (sfException $e)
{
$e->printStackTrace();
}
catch (Exception $e)
{
sfException::createFromException($e)->printStackTrace();
}
return true;
}
// see if the file exists in the current module lib directory
if (
sfContext::hasInstance()
&&
($module = sfContext::getInstance()->getModuleName())
&&
isset($this->classes[$module.'/'.$class])
)
{
try
{
require $this->classes[$module.'/'.$class];
}
catch (sfException $e)
{
$e->printStackTrace();
}
catch (Exception $e)
{
sfException::createFromException($e)->printStackTrace();
}
return true;
}
return false;
}
クラス名や呼び出し方などは大幅に変わってはいたけれども、実体の大筋は変わってない感じ。
フレームワークを使わずにちょっと書きたいプログラムの場合、いちいちキャッシュするのは面倒なので、出来れば動的にrequireしていきたいのだけど…
例えば、こんな感じのを書いてみた。
autoload.php
function __autoload($class)
{
//extension setting
$ext = '.class.php';
//direcotories setting
$dirs = array(
'',
'lib/',
'lib/actions/',
'lib/dao/',
'lib/dto/',
'lib/entity/'
);
//class already exists
if(class_exists($class, false) || interface_exists($class, false))
return;
//search class file in directries
foreach($dirs as $dir):
$file = $dir . $class . $ext;
if(is_file($dir . $class . $ext)):
require $file;
return;
endif;
endforeach;
}
見ての通り、拡張子とディレクトリを直接設定するので汎用性はないです。
マニュアルのアイディアを参考にしつつ__autoloadではなくspl_autoload_registerを使うとこうか。
spl_autoload_register使用
function autoLoader($class)
{
//extension setting
$ext = '.class.php';
//direcotories setting
$dirs = array(
'',
'lib/',
'lib/actions/',
'lib/dao/',
'lib/dto/',
'lib/entity/'
);
//class already exists
if(class_exists($class, false) || interface_exists($class, false))
return;
//search class file in directries
foreach($dirs as $dir):
$file = $dir . $class . $ext;
if(is_file($dir . $class . $ext)):
require $file;
return;
endif;
endforeach;
}
spl_autoload_register('autoLoader');
spl_autoload_registerを使う理由は、__autoloadが一度しか設定できないのに対し、spl_autoload_registerは複数回指定できるから。もちろん、Symfonyでもそうしてる。ただし、spl_autoload_registerを使うともし既存の__autoload()があった場合使用できなくなるので、以下のように登録しておく必要があるとのこと。なるほど。
if(function_exists('__autoload')):
spl_autoload_register('__autoload');
endif;
フレームワークでは通常あり得ないけど、場当たり的に書いたプログラム群だったらあり得るかも。
spl_autoload_registerのマニュアルにあるサンプルはSymfonyの処理に近いな。
こんなの。
spl_autoload_registerのマニュアルにあるサンプル
* Or you can use an instance :
なるほど。
クラス内に隠蔽するのは大事かも。
というわけで、暫定。
autoload.php
class MyAutoload
{
public static function autoload($class)
{
//extension setting
$ext = '.class.php';
//direcotories setting
$dirs = array(
'',
'lib/',
'lib/actions/',
'lib/dao/',
'lib/dto/',
'lib/entity/'
);
//class already exists
if(class_exists($class, false) || interface_exists($class, false))
return;
//search class file in directries
foreach($dirs as $dir):
$file = $dir . $class . $ext;
if(is_file($dir . $class . $ext)):
require $file;
return;
endif;
endforeach;
}
}
//if __autoload already exists
if(function_exists('__autoload')):
spl_autoload_register('__autoload');
endif;
spl_autoload_register(array('MyAutoload', 'autoload'));