【メモ】 autoloadの実装について。


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;
}

手順は以下の通り。

  1. autoload.ymlから生成されたクラスリストファイルを読み込んでself::$classesに格納
  2. (class_exists($class, false))でチェック。既に呼び出されていれば終了(trueを返す)。第2引数にfalseをセットすることで__autoloadを呼ばないようにする。
  3. 指定したクラスがクラスリストにあればrequire(trueを返す)。
  4. なければモジュールクラスを検索しあればrequire(trueを返す)。
  5. 読み込みに失敗したら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'));