CodeIgniterの学習 50 - HMVCモジュール拡張のインストールと改造をする( HMVCを使ってブログパーツチックにコントローラやビュー内で別コントローラ(とそのビュー)を呼べるようにするその2)

今日は昨日のエントリーの続き。CodeIgniterのHMVC拡張をおこなう。(PHP5用)

ついでに一部不満な箇所を微改造。(モデル等のファイル名の大文字小文字を区別するところ等)

(今日のエントリーはインストールと微改造まで、続きは明日)

インストール

設置するソースは3ファイル

手順1)
元ネタの Modular Extensions - HMVC からソースをダウンロードする。

http://codeigniter.com/wiki/Modular_Extensions_-_HMVC/
Download new version 5.1: File:modular extensions 5140.zip (PHP5 only)
からダウンロードできる。



手順2)
解凍したファイルの

を、application/libraries に設置する。

インストールは以上。



これで既存のコントローラの設置場所以外に、

application/modules/モジュール名/controllers/モジュール名.php
のコントローラが
http://example.com/モジュール名/(メソッド名)/
で呼べるようになる。
機能毎にソース分割できるね。


またコントローラの中で別のコントローラを呼び出すことも出来るようになる。


モジュール内のソース体系の流儀は、CodeIgniter標準と同様に

application/modules/モジュール名/libraries/
application/modules/モジュール名/models/
application/modules/モジュール名/views/
application/modules/モジュール名/language/

みたいにモジュール内専用の ライブラリ、モデル、ビュー、言語ファイル
を任意で設置出来る。(libraries/が不要ならディレクトリは存在しなくて良い)


不満な箇所の微改造その1、読み込むファイル名先頭の大文字小文字を同一として扱う

現状バージョンのHMVC拡張だと、
モデル等の物理ファイル名の先頭の大文字小文字と、
CIにロードする時の引数の大文字小文字がちぐはぐな時に
読み込み出来なくてエラーになったりする。


ちなみに、CodeIgniter 1.7標準のローダー(system/libraries/Loader.php
では、ファイル名先頭の大文字小文字は区別しないようになっている。

// We test for both uppercase and lowercase, for servers that
// are case-sensitive with regard to file names

ていうのがCI標準のローダー内の_ci_init_class() 等に見受けられるのだが、
インストールしたHMVC拡張のソースのローダー部分(application/libraries/Modules.php
にはその処理が入っていない。


このせいで、既存のソース(例えばFreak_Auth等)を使っていると、ロード時にエラーになって動作しなくて困るので、
CodeIgniter 1.7標準のローダーに習い、以下の箇所を改造してみた。

(08/12/02 改造箇所追加、変更により記述書き換え)



修正ファイル1:application/libraries/Modules.php (modular extensions 5140)
(90行目付近から抜粋)

<?php
//上略

  /** Library base class autoload **/
  public static function autoload($class)
  {

    /* don't autoload CI_ or MY_ prefixed classes */
    if (strstr($class, 'CI_') OR strstr($class, 'MY_')) return;

    //ファイル名先頭の大文字込み時を区別しないようにした
    //if(is_file($location = APPPATH.'libraries/'.$class.EXT))
    //  include_once $location;
    $file = str_replace(EXT, '', $class);
    $location = APPPATH.'libraries/'.$file.EXT;
    if(is_file($location)){
      include_once $location;
    }elseif ( file_exists(  APPPATH . 'libraries/' . strtolower( $file ) . EXT ) ) {
      include_once APPPATH . 'libraries/' . strtolower( $file ) . EXT;
    }elseif ( file_exists( APPPATH . 'libraries/'. ucfirst( strtolower( $file ) ) . EXT ) ) {
      include_once APPPATH . 'libraries/'. ucfirst(strtolower( $file ) ) . EXT;
    }else{
      //show_error( "{$location} does not found" );
    }
    //ファイル名先頭の大文字込み時を区別しないようにした end
  }

  /** Load a module file **/
  public static function load_file($file, $path, $type = 'other', $result = TRUE)
  {


    $file = str_replace(EXT, '', $file);
    $location = realpath($path.$file.EXT);

    if ($type === 'other')
    {
      if (class_exists($file, FALSE))
      {
        log_message('debug', "File already loaded: {$location}");
        return $result;
      }
      //ファイル名先頭の大文字込み時を区別しないようにした
      //include_once $location;
      if ( file_exists( $path . strtolower( $file ) . EXT ) ) {
        include_once $path . strtolower( $file ) . EXT;
      }elseif ( file_exists( $path . ucfirst( strtolower( $file ) ) . EXT ) ) {
        include_once $path . ucfirst( strtolower( $file ) ) . EXT;
      }else {
        show_error( "{$path}{$file}" . EXT . " does not found" );
      }
      //ファイル名先頭の大文字込み時を区別しないようにした end
    }
    else /* load config or language arrays */
    {
      //ファイル名先頭の大文字込み時を区別しないようにした
      include $location;
      if ( ! isset($$type) OR ! is_array($$type))
        show_error("{$location} does not contain a valid {$type} array");
      $result = $$type;
      /*
      if ( file_exists( $path . strtolower( $file ) . EXT ) ) {
        include $path . strtolower( $file ) . EXT;
      }elseif ( file_exists( $path . ucfirst( strtolower( $file ) ) . EXT ) ) {
        include $path . ucfirst( strtolower( $file ) ) . EXT;
      }else {
        show_error( "{$path}{$file}" . EXT . " does not found" );
      }
      $result = $$type;
      */
      //ファイル名先頭の大文字込み時を区別しないようにした end
    }

    log_message('debug', "File loaded: {$location}");
    return $result;
  }

  /**
  * Find a file
  * Scans for files located within application/modules directory.
  * Also scans application directories for models and views.
  * Generates fatal error on file not found.
  **/
  public static function find($file, $path, $base, $subpath = NULL)
  {

    $search = array();

    /* override subpath when loading from a subdirectory */
    if ($pos = strrpos($file, '/'))
      {
      $subpath = substr($file, 0, $pos);
      $file = substr($file, $pos + 1);
      }

    ($path) AND $path .= '/';
    ($subpath) AND $subpath .= '/';

    $file_ext = strpos($file, '.') ? $file : $file.EXT;

    if ($path) /* ensure we have a module path */
    {
      ($subpath) AND $search[] = MODBASE.$path.$base.$subpath;
      $search[] = MODBASE.$path.$base;
    }

    switch ($base)
    {
      case ('libraries/'):

        $file_ext = ucfirst($file_ext);
        break;

      case ('views/'):

      case ('models/'):

        ($subpath) AND $search[] = APPPATH.$base.$subpath;
        $search[] = APPPATH.$base;
        ($path) AND $search[] = APPPATH.$base.$path;
    }

    foreach ($search as $path2)
    {
      //ファイル名先頭の大文字込み時を区別しないようにした
      //if (is_file($path2.$file_ext)) return array($path2, $file);
      if(is_file($path2.$file_ext)){
        return array($path2, $file);
      }elseif ( is_file( $path2 . strtolower( $file_ext ) ) ) {
        return array( $path2, strtolower( $file ) );
      }else {
        if ( is_file( $path2 . ucfirst( strtolower( $file_ext ) ) ) ) {
          return array( $path2, ucfirst( strtolower( $file ) ) );
        }
      }
      //ファイル名先頭の大文字込み時を区別しないようにした end
    }

    /* file not found. show an error for these types */
    if ($base == 'views/' OR $base == 'models/')
      show_error("Unable to locate the file: {$file_ext} in: {$base}");

    /* handle the rest back in the controller */
    return array(FALSE, $file);
  }

//下略
 ?>

(変更点は日本語コメントで挟み込んでいる間の箇所)


修正ファイル2:application/libraries/Controller.php(modular extensions 5140)
(325行目付近から抜粋)

<?php
//上略
  public function model($model, $object_name = NULL, $connect = FALSE)
  {
    $ci = modules::$registry[$this->_class];

    //モデル名は勝手に小文字には変えない
    //($_alias = $object_name) OR $_alias = strtolower(end(explode('/', $model)));
    ($_alias = $object_name) OR $_alias = end(explode('/', $model));
    //モデル名は勝手に小文字には変えないend

    if (isset(self::$models[$_alias]))
      return $ci->$_alias;

    list($path, $model) = modules::find($model, $this->_module, 'models/');

    (class_exists('Model', FALSE)) OR load_class('Model', FALSE);

    if ($connect !== FALSE)
    {
      if ($connect === TRUE) $connect = '';
      $this->database($connect, FALSE, TRUE);
    }

    modules::load_file($model, $path);

    $model = ucfirst($model);
    $ci->$_alias = new $model();
    self::$models[$_alias] = $ci->$_alias;

    $ci->$_alias->_assign_libraries();

    return $ci->$_alias;
  }
//下略
 ?>

(変更点は日本語コメントで挟み込んでいる間の箇所)

これで既存のソースは変えなくても(ほぼ)HMVCは動作するようになった。

(08/12/02 追記 うーん(Freak_Authとはいろいろと相性悪いな。まあ改造するから良いけど))



次はHMVC拡張を使って、テストモジュールのサンプルを作って動作確認してみる。+ついでにURLを直接叩かれたくないモジュールのルーティングの調整を行う。


記録し始めてなんだかんだで2ヶ月ちょっと経ったな。一応実務でも実験的に使っている。
昔のエントリーでタスクリストを作るとか書いたけど、放置状態だな。ま、いっか。