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のマニュアルにあるサンプル

<?php
class MyClass {
  public static function autoload($className) {
    // ...
  }
}

spl_autoload_register(array('MyClass', 'autoload'));
?>

* Or you can use an instance :
<?php
class MyClass {
  public function autoload($className) {
    // ...
  }
}

$instance = new MyClass();
spl_autoload_register(array($instance, 'autoload'));
?>


なるほど。

クラス内に隠蔽するのは大事かも。




というわけで、暫定。


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'));