CodeIgniterの学習 72 - HMVC(モジュール機構用ライブラリ)をversion 5.2.14にアップして、モジュール内のコントローラ構成をサブディレクトリまで掘れるようにする。(+ファイル名大文字小文字判別しない俺俺改造)

こんにちは!
今日はHMVCモジュール拡張ライブラリのバージョンをアップしてみる。
ここ最近のエントリーで、ci-cmsをベースにいろいろ魔改造して弄ってやろうとしている一環。


twitterにも愚痴を書いたけど、MatchBoxのモジュール拡張って動作が1.5倍〜2倍くらい遅いのね。
多分理由はモジュール構成の呼び出し元判定にdebug_backtrace()とか呼んで使っているから&ループ&ファイル読み込み試行回数が多めだからか。
(細かい検証はしていないが)


せっかくだから、ci-cmsのモジュール機能部分をMatchBoxからHMVCに入れ替え&HMVCのバージョンアップをしてみた。
(以前のエントリー http://d.hatena.ne.jp/dix3/20081129/1227964872 とかその前後からHMVCのバージョンもだいぶ上がって中身が変わっている。)


これまではMatchBoxだと、application/modules/hogehoge(モジュール名)/controllers/admin/mogefuga.php
とかのディレクトリ構成でも呼べてたけど、HMVC(version 5.1.40)ではモジュール内コントローラ内サブディレクトリ内コントローラは呼べなかった。(モジュール名と異なるコントローラファイルも呼べなかったっけ)。


けどHMVC(version 5.2.14)にバージョンアップするとHMVCでも同様にモジュール内コントローラ内サブディレクトリも、モジュール名と異なるコントローラファイルも使えるようになる。
(参考:HMVC structure and admin module - http://codeigniter.com/forums/viewthread/115623/


→ci-cmsのMatchBoxからの置き換えでボトルネック解消(゜Д゜)ウマー
view内で別コントローラを直接呼べてビューからモジュール直接呼び出しの手順が楽になって(゜Д゜)ウマーって感じ。( modules::run('mdl_moemoe') みたいに呼べる。)





(2009/09/08追記:モジュール内configディレクトリ内configファイルの読み出しについて)

このエントリー記述当時のHMVC(version 5.2.14)ではモジュール内コントローラ内呼び出しはOK、モジュール内モデル内呼び出しはNG
HMVC(version 5.2.15)(2009-09-05リリース)から、モジュール内コントローラ内呼び出しもOK、モジュール内モデル内呼び出しもOKです。
5.2.14 → 5.2.15(2009-09-05リリース)への俺俺改造込みのVerアップは適当にWinMergeでマージでもしてくださいな。(欲しい人がいれば貼りますが地球上に居るのか?)

ただし、HMVCを使っている場合、
モデル内でモジュール名/config/hoge.phpを読みたいときは、

↓OK:動く

$this -> load -> config( 'hoge', true );
$aaa = $this->config->item('hoge');

↓NG:動かない(マニュアル上はこっちが正しい)

$this -> config -> load( 'hoge', true );
$aaa = $this->config->item('hoge');

と、loadが先です。HMVCを使わない時はどっちでもいけたけど。(ややこしい)
悩むのが嫌なので、私は$this -> load -> config でいきます。


↓上記の理由によりこれは間違い、
ちなみにVerアップすると、モジュールディレクトリ内/config/hoge.phpを読んでくれなくなった。(泣)。
設定ファイルはモジュール内に入れるなって事かな?追うのが面倒なので取りあえず放置。一応ご注意を。
ci-cmsではコントローラ内サブディレクトリは必要なので、このまま続行。いつかついでに改造するかも。

(2009/09/08追記終わり)


(2009/09/12追記 ci-cmsでHMVCを使う場合の注意、※ci-cms以外は関係ないかも)
ci-cmsでは modules/hoge/admin/fuga.phpをルーティング変更で
ttp://example.com/admin/hoge/fuga
で呼べるようになっているところがあるのだが、この条件のとき

HVMCのapplication/libraries/Controller.phpのMX_Loader::viewメソッドで、

<?php
//(上略)
  /**
   * * Load a module view *
   */ 
  public function view( $view, $vars = array(), $return = FALSE )
  { 
    list($path, $view) = Modules::find($view, $this->_module, 'views/');
    $this->_ci_view_path = $path;
    return parent :: _ci_load( array( '_ci_view' => $view, '_ci_vars' => parent :: _ci_object_to_array( $vars ), '_ci_return' => $return ) );
  }
//(下略)
?>

の箇所を

<?php
//(上略)
  /**
   * * Load a module view *
   */ 
  # あるとルーティングがうまくいかないので外す
  public function view( $view, $vars = array(), $return = FALSE )
  { 
    # list($path, $view) = Modules::find($view, $this->_module, 'views/');
    # $this->_ci_view_path = $path;
    return parent :: _ci_load( array( '_ci_view' => $view, '_ci_vars' => parent :: _ci_object_to_array( $vars ), '_ci_return' => $return ) );
  }
//(下略)
?>

に変えておかないと、このviewが呼び出せないでハマルので注意。

但しこれを変えるとci-cmsのview呼び出し方式(ci-cms独自のLayoutライブラリ拡張ベースのview呼び出し)
以外で作った過去モジュールのviewが探せない。

(本当は上の2行をコメントアウトじゃなくて、このfind部分を改造すれば良いんだけど、とりあえず後回し。)

ルーティングをこねくり回している環境によってはハマルと思うので注意してください。

また、最近HMVC本体の更新ペースが速いので、入れ替える際にそちらも注意。
(2009/09/12追記終わり)


(2009/09/19追記 ci-cmsでHMVCを使う場合の注意その2、※ci-cms以外は関係ないかも)
adminのビューのパス検索がHMVCだとうまくいかないので、ちょっと格好悪いがべた書きで条件分岐してみた。
とりあえずci-cmsのルーティングだとこれで無事動いている。

HVMCのapplication/libraries/Controller.phpのMX_Loader::viewメソッド(ci-cms用に改造)

<?php
//上略
  /**
   * * Load a module view *
   */
  # ルーティング周りに絡む不具合をci-cmsにあわせて修正
  public function view( $view, $vars = array(), $return = FALSE )
  {
    if(strstr($view,'admin')){
      #取りあえずadminのルーティングの時だけviewのベースパスを直接指定するようにした。
      $this->_ci_view_path ='../application/views/';
    }else{
      #通常はモジュール内のビューを探す(HMVCのモジュール検索の動きのまま)
      list($path, $view) = Modules::find($view, $this->_module, 'views/');
      $this->_ci_view_path = $path;
    }
    return parent :: _ci_load( array( '_ci_view' => $view, '_ci_vars' => parent :: _ci_object_to_array( $vars ), '_ci_return' => $return ) );
  }
//下略

?>

また、私の環境では、Controllerクラスを独自拡張しているのだが、
application/libraries/Controller.php(HMVCのソース)と同じファイル内の末尾に、
application/libraries/Module_Controller.php を作り、

class Module_Controller extends Controller {
…
俺俺コントローラ(いろんな共通メソッドやら、なんやらを記述)
…

の様に独自コントローラを記述してモジュール用のコントローラは、これをextendsしている。
これで取りあえず動いている。
(CIの流儀の1ファイル1クラスとちょっと違うがそこは妥協ってことで)
(不具合デバグ中にソースを寄せてデバッグしたのを戻し忘れてただけだった…コントローラファイルの場所。)


(2009/09/19追記終わり)





ソース(必須入れ替え部分)

必要な人なんて殆どいなそーだが、改造ソースを全部貼っておく。いつものことながら無保証。

元ネタは、http://codeigniter.com/wiki/Modular_Extensions_-_HMVC/
のversion 5.2.14(PHP5用)です。(PHP5だけどvarとか使っているのは気にしないでください。publicです。)

設置する手順や概念は、以前のエントリーを参照ください。



モデル名等の大文字小文字識別、ライブラリファイル名の大文字小文字識別は外しています。
(大文字小文字の違いで動かねー!って悩まされたくないので。これでも大体動く、多分。)


phpCBでフォーマット済み、末尾の閉じタグはブログ掲載の都合上付けているだけなので取っ払ってください。
変更箇所は日本語でコメントを書いている周辺部分。



改造ソース1:application/libraries/Modules.php

<?php ( defined( 'BASEPATH' ) ) OR exit( 'No direct script access allowed' );
/**
 * PHP5 spl_autoload
 */
spl_autoload_register( 'Modules::autoload' );

/**
 * Modular Extensions - PHP5
 *
 * Adapted from the CodeIgniter Core Classes
 *
 * @copyright Copyright (c) 2006, EllisLab, Inc.
 * @link http://codeigniter.com Description:
This library provides functions to load and instantiate controllers
and module controllers allowing use of modules and the HMVC design pattern.

Install this file as application/libraries/modules.php
 * @copyright Copyright (c) Wiredesignz 2009-08-22
 * @version 5.2.14

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */
class Modules {
  public static $registry = array();

  /**
   * Run a module controller method
   * Output from module is buffered and returned.
   */
  public static function run( $module )
  {
    $method = 'index';

    if ( ( $pos = strrpos( $module, '/' ) ) != FALSE ) {
      $method = substr( $module, $pos + 1 );
      $module = substr( $module, 0, $pos );
    }

    $controller = end( explode( '/', $module ) );
    $module_controller = ( $controller == $module ) ? $controller : $module . '/' . $controller;

    if ( $class = self :: load( $module_controller ) ) {
      if ( method_exists( $class, $method ) ) {
        ob_start();
        $args = func_get_args();
        $output = call_user_func_array( array( $class, $method ), array_slice( $args, 1 ) );
        $buffer = ob_get_clean();
        return ( $output ) ? $output : $buffer;
      }

      log_message( 'debug', "Module failed to run: {$module}/{$controller}/{$method}" );
    }
  }

  /**
   * * Load a module controller *
   */
  public static function load( $module )
  {
    ( is_array( $module ) ) AND list( $module, $params ) = each( $module ) OR $params = NULL;

    /**
     * get the controller class name
     */
    $class = strtolower( end( explode( '/', $module ) ) );

    /**
     * return an existing controller from the registry
     */
    if ( isset( self :: $registry[$class] ) ) return self :: $registry[$class];

    /**
     * get the module path
     */
    $segments = explode( '/', $module );

    /**
     * find the controller
     */
    list( $class ) = CI :: $APP -> router -> locate( $segments );

    /**
     * set the module directory
     */
    $path = APPPATH . 'controllers/' . CI :: $APP -> router -> fetch_directory();

    /**
     * load the controller class
     */
    self :: load_file( $class, $path );

    /**
     * create the new controller
     */
    $controller = ucfirst( $class );
    $controller = new $controller( $params );
    return $controller;
  }

  /**
   * * 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 = $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 array
       */
      include $location;

      if ( ! isset( $$type ) OR ! is_array( $$type ) ) {
        show_error( "{$location} does not contain a valid {$type} array" );
      }
      $result = $$type;
    }
    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, $module, $base, $subpath = NULL )
  {
    // var_dump(array($file, $module, $base, $subpath));
    /**
     * is subpath in filename?
     */
    if ( ( $pos = strrpos( $file, '/' ) ) !== FALSE ) {
      $subpath = substr( $file, 0, $pos );
      $file = substr( $file, $pos + 1 );
    }

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

    /**
     * set the module path(s) to search
     */
    $modules = ( $module ) ? array( MODBASE . $module . '/' ) : array();

    /**
     * $subpath is a module dir? or is a sub-directory
     */
    if ( is_dir( MODBASE . $subpath . $base ) ) {
      $modules[] = MODBASE . $subpath;
    }else {
      $path .= $subpath;
    }

    $file_ext = strpos( $file, '.' ) ? $file : $file . EXT;
    // ここでライブラリのファイル名は先頭を大文字固定にしているが、色々面倒なので小文字でもOKとする。
    if ( $base == 'libraries/' ) $file_ext = ucfirst( $file_ext );

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

    /**
     * look in the application directory for views or models
     */
    if ( $base == 'views/' OR $base == 'models/' ) {
      if ( is_file( APPPATH . $path . $file_ext ) ) return array( APPPATH . $path, $file );
      show_error( "Unable to locate the file: {$file_ext} in {$module}/{$path}" );
    }

    return array( FALSE, $file );
  }
} //endofclass

/* End of file Modules.php */
/* Location: ./application/libraries/Modules.php */

 ?>




改造ソース2:application/libraries/Controller.php
(451行目付近 var $template = array();はci-cmsで必要、本来不要。)

<?php ( defined( 'BASEPATH' ) ) OR exit( 'No direct script access allowed' );
/**
 * Modular Extensions - PHP5
 * 
 * Adapted from the CodeIgniter Core Classes
 * 
 * @copyright Copyright (c) 2006, EllisLab, Inc.
 * @link http://codeigniter.com Description:
This library replaces the CodeIgniter Controller class
and adds features allowing use of modules and the HMVC design pattern.

Install this file as application/libraries/Controller.php
 * @copyright Copyright (c) Wiredesignz 2009-08-22
 * @version 5.2.14

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */
class CI extends CI_Base {
  public static $APP;

  public function __construct()
  {
    parent :: __construct();

    /**
     * assign CI instance
     */
    self :: $APP = $this;

    /**
     * assign the core loader
     */
    $this -> load = load_class( 'Loader' );

    /**
     * the core classes
     */
    $classes = array( 'config' => 'Config',
      'input' => 'Input',
      'benchmark' => 'Benchmark',
      'uri' => 'URI',
      'output' => 'Output',
      'lang' => 'Language',
      'router' => 'Router', 
      );

    /**
     * assign the core classes
     */
    foreach ( $classes as $key => $class ) {
      $this -> $key = load_class( $class );
    }

    /**
     * autoload application items
     */
    $this -> load -> _ci_autoloader();
  }

  public static function instance()
  {
    is_a( self :: $APP, __CLASS__ ) OR new CI;
    return self :: $APP;
  }
}

/**
 * create the application object
 */
CI :: instance();

/**
 * load the modules library
 */
include 'Modules.php';

class MX_Loader extends CI_Loader {
  private $_module;

  public function __construct()
  {
    $this -> load = $this;

    /**
     * this module name
     */
    $this -> _module = CI :: $APP -> router -> fetch_module();

    /**
     * ci loader references
     */
    foreach ( get_class_vars( 'CI_Loader' ) as $var => $val ) {
      $this -> $var = &CI :: $APP -> load -> $var;
    }
  }

  /**
   * * Load a module config file *
   */
  public function config( $file = '', $use_sections = FALSE )
  {
    ( $file == '' ) AND $file = 'config';

    if ( in_array( $file, CI :: $APP -> config -> is_loaded, TRUE ) )
      return CI :: $APP -> config -> item( $file );

    list( $path, $file ) = Modules :: find( $file, $this -> _module, 'config/' );

    if ( $path === FALSE ) {
      parent :: config( $file, $use_sections );
      return CI :: $APP -> config -> item( $file );
    }

    if ( $config = Modules :: load_file( $file, $path, 'config' ) ) {
      /**
       * reference to the config array
       */
      $current_config = &CI :: $APP -> config -> config;

      if ( $use_sections === TRUE ) {
        if ( isset( $current_config[$file] ) ) {
          $current_config[$file] = array_merge( $current_config[$file], $config );
        }else {
          $current_config[$file] = $config;
        }
      }else {
        $current_config = array_merge( $current_config, $config );
      }
      CI :: $APP -> config -> is_loaded[] = $file;
      unset( $config );
      return CI :: $APP -> config -> item( $file );
    }
  }

  /**
   * * Load the database drivers *
   */
  public function database( $params = '', $return = FALSE, $active_record = FALSE )
  {
    if ( class_exists( 'CI_DB', FALSE ) AND $return == FALSE AND $active_record == FALSE )
      return;

    require_once BASEPATH . 'database/DB' . EXT;

    if ( $return === TRUE )
      return DB( $params, $active_record );

    CI :: $APP -> db = DB( $params, $active_record );
    $this -> _ci_assign_to_models();
    return CI :: $APP -> db;
  }

  /**
   * * Load a module helper *
   */
  public function helper( $helper )
  {
    if ( is_array( $helper ) )
      return $this -> helpers( $helper );

    if ( isset( $this -> _ci_helpers[$helper] ) )
      return;

    list( $path, $_helper ) = Modules :: find( $helper . '_helper', $this -> _module, 'helpers/' );

    if ( $path === FALSE )
      return parent :: helper( $helper );

    Modules :: load_file( $_helper, $path );
    $this -> _ci_helpers[$_helper] = TRUE;
  }

  /**
   * * Load an array of helpers *
   */
  public function helpers( $helpers )
  {
    foreach ( $helpers as $_helper ) $this -> helper( $_helper );
  }

  /**
   * * Load a module language file *
   */
  public function language( $langfile, $lang = '' )
  {
    $deft_lang = CI :: $APP -> config -> item( 'language' );
    $idiom = ( $lang == '' ) ? $deft_lang : $lang;

    if ( in_array( $langfile . '_lang' . EXT, CI :: $APP -> lang -> is_loaded, TRUE ) )
      return CI :: $APP -> lang;

    list( $path, $_langfile ) = Modules :: find( $langfile . '_lang', $this -> _module, 'language/', $idiom );

    if ( $path === FALSE ) {
      parent :: language( $langfile, $lang );
    }else {
      if ( $lang = Modules :: load_file( $_langfile, $path, 'lang' ) ) {
        CI :: $APP -> lang -> language = array_merge( CI :: $APP -> lang -> language, $lang );
        CI :: $APP -> lang -> is_loaded[] = $langfile . '_lang' . EXT;
        unset( $lang );
      }
    }
    return CI :: $APP -> lang;
  }

  /**
   * * Load a module library *
   */
  public function library( $library, $params = NULL, $object_name = NULL )
  {
    $class = strtolower( end( explode( '/', $library ) ) );

    if ( isset( $this -> _ci_classes[$class] ) AND $_alias = $this -> _ci_classes[$class] )
      return CI :: $APP -> $_alias;

    ( $_alias = $object_name ) OR $_alias = $class;
    list( $path, $_library ) = Modules :: find( $library, $this -> _module, 'libraries/' );

    /**
     * load library config file as params
     */
    if ( $params == NULL ) {
      list( $path2, $file ) = Modules :: find( $_alias, $this -> _module, 'config/' );
      ( $path2 ) AND $params = Modules :: load_file( $file, $path2, 'config' );
    }

    if ( $path === FALSE ) {
      parent :: _ci_load_class( $library, $params, $object_name );
      $_alias = $this -> _ci_classes[$class];
    }else {
      Modules :: load_file( $_library, $path );
      $library = ucfirst( $_library );
      CI :: $APP -> $_alias = new $library( $params );
      $this -> _ci_classes[$class] = $_alias;
    }

    $this -> _ci_assign_to_models();
    return CI :: $APP -> $_alias;
  }

  /**
   * * Load a module model *
   */
  public function model( $model, $object_name = NULL, $connect = FALSE )
  {
    if ( is_array( $model ) )
      return $this -> models( $model ); 
    // モデル名は勝手に小文字には変えない
    // ($_alias = $object_name) OR $_alias = strtolower(end(explode('/', $model)));
    ( $_alias = $object_name ) OR $_alias = end( explode( '/', $model ) ); 
    // モデル名は勝手に小文字には変えないend
    if ( in_array( $_alias, $this -> _ci_models, TRUE ) ) {
      return CI :: $APP -> $_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 :: $APP -> $_alias = new $model();
    $this -> _ci_models[] = $_alias;

    CI :: $APP -> $_alias -> _assign_libraries();
    return CI :: $APP -> $_alias;
  }

  /**
   * * Load an array of models *
   */
  function models( $models )
  {
    foreach ( $models as $_model ) $this -> model( $_model );
  }

  /**
   * * Load a module controller *
   */
  public function module( $module, $params = NULL )
  {
    if ( is_array( $module ) )
      return $this -> modules( $module );

    $_alias = strtolower( end( explode( '/', $module ) ) );
    CI :: $APP -> $_alias = Modules :: load( array( $module => $params ) );
    return CI :: $APP -> $_alias;
  }

  /**
   * * Load an array of controllers *
   */
  public function modules( $modules )
  {
    foreach ( $modules as $_module ) $this -> module( $_module );
  }

  /**
   * * Load a module plugin *
   */
  public function plugin( $plugin )
  {
    if ( isset( $this -> _ci_plugins[$plugin] ) )
      return;

    list( $path, $_plugin ) = Modules :: find( $plugin . '_pi', $this -> _module, 'plugins/' );

    if ( $path === FALSE )
      return parent :: plugin( $plugin );

    Modules :: load_file( $_plugin, $path );
    $this -> _ci_plugins[$plugin] = TRUE;
  }

  /**
   * * Load a module view *
   */
  public function view( $view, $vars = array(), $return = FALSE )
  {
    list( $path, $view ) = Modules :: find( $view, $this -> _module, 'views/' );
    $this -> _ci_view_path = $path;
    return parent :: _ci_load( array( '_ci_view' => $view, '_ci_vars' => parent :: _ci_object_to_array( $vars ), '_ci_return' => $return ) );
  }

  /**
   * * Assign libraries to models *
   */
  public function _ci_assign_to_models()
  {
    foreach ( $this -> _ci_models as $model ) {
      CI :: $APP -> $model -> _assign_libraries();
    }
  }

  public function _ci_is_instance()
  {
  }

  protected function __get( $var )
  {
    return CI :: $APP -> $var;
  }

  /**
   * * Autload items *
   */
  public function _ci_autoloader( $autoload = array(), $path = FALSE )
  {
    if ( $this -> _module )
      list( $path, $file ) = Modules :: find( 'autoload', $this -> _module, 'config/' );

    /**
     * module autoload file
     */
    if ( $path != FALSE )
      $autoload = array_merge( Modules :: load_file( $file, $path, 'autoload' ), $autoload );

    /**
     * nothing to do
     */
    if ( count( $autoload ) == 0 ) return;

    /**
     * autoload config
     */
    if ( isset( $autoload['config'] ) ) {
      foreach ( $autoload['config'] as $key => $val ) {
        $this -> config( $val );
      }
    }

    /**
     * autoload helpers, plugins, languages
     */
    foreach ( array( 'helper', 'plugin', 'language' ) as $type ) {
      if ( isset( $autoload[$type] ) ) {
        foreach ( $autoload[$type] as $item ) {
          $this -> $type( $item );
        }
      }
    }

    /**
     * autoload database & libraries
     */
    if ( isset( $autoload['libraries'] ) ) {
      if ( in_array( 'database', $autoload['libraries'] ) ) {
        /**
         * autoload database
         */
        if ( ! $db = CI :: $APP -> config -> item( 'database' ) ) {
          $db['params'] = 'default';
          $db['active_record'] = TRUE;
        }
        $this -> database( $db['params'], FALSE, $db['active_record'] );
        $autoload['libraries'] = array_diff( $autoload['libraries'], array( 'database' ) );
      }

      /**
       * autoload libraries
       */
      foreach ( $autoload['libraries'] as $library ) {
        $this -> library( $library );
      }
    }

    /**
     * autoload models
     */
    if ( isset( $autoload['model'] ) ) {
      foreach ( $autoload['model'] as $model => $alias ) {
        ( is_numeric( $model ) ) ? $this -> model( $alias ) : $this -> model( $model, $alias );
      }
    }

    /**
     * autoload module controllers
     */
    if ( isset( $autoload['modules'] ) ) {
      foreach ( $autoload['modules'] as $controller ) {
        ( $controller != $this -> _module ) AND $this -> module( $controller );
      }
    }
  }
}

class Controller {
  /**
   * * PHP4 compatibility *
   */
  var $template = array();#ci-cms用、ci-cms以外ではこの行は不要
  public function Controller()
  {
    /**
     * set the loader
     */
    $this -> load = new MX_Loader;

    $class = strtolower( get_class( $this ) );
    log_message( 'debug', ucfirst( $class ) . " Controller Initialized" );

    /**
     * register this controller
     */
    Modules :: $registry[$class] = $this;

    /**
     * autoload module items
     */
    $autoload = isset( $this -> autoload ) ? $this -> autoload : array();
    $this -> load -> _ci_autoloader( $autoload );
  }

  protected function __get( $var )
  {
    return CI :: $APP -> $var;
  }
}

/**
 * autoload MX_Controller base classes if available
 */
if ( is_file( $location = APPPATH . 'libraries/MX_Controller' . EXT ) ) {
  include_once $location;
}

/* End of file Controller.php */
/* Location: ./application/libraries/Controller.php */

 ?>




ソース(任意入れ替え部分)


改造ソース3:application/libraries/MY_Router.php

このソースは、以前のエントリーでの拡張部分なので、本来は変更する必要なし。
HMVC version 5.2.14 のソースをそのまま使ってください。


(参考1:http://d.hatena.ne.jp/dix3/20081201/1228113261 -
CodeIgniterの学習 51 - HMVCモジュールの動作確認をしてみる( HMVCを使ってブログパーツチックにコントローラやビュー内で別コントローラ(とそのビュー)を呼べるようにするその3))
(参考2: http://d.hatena.ne.jp/dix3/20081209/1228816805 -
CodeIgniterの学習 53 - HMVCモジュールを改造してコントローラ単位(モジュール単位)での使用・不使用制御をconfigファイルで行えるようにする。)

での拡張部分

<?php ( defined( 'BASEPATH' ) ) OR exit( 'No direct script access allowed' );
/**
 * define the modules base path
 */
define( 'MODBASE', APPPATH . 'modules/' );

/**
 * define the offset from application/controllers
 */
define( 'MODOFFSET', '../modules/' );

/**
 * Modular Extensions - PHP5
 * 
 * Adapted from the CodeIgniter Core Classes
 * 
 * @copyright Copyright (c) 2006, EllisLab, Inc.
 * @link http://codeigniter.com Description:
This library extends the CodeIgniter router class.
and adds features allowing use of the HMVC design pattern.
Install this file as application/libraries/MY_Router.php
 * @copyright Copyright (c) Wiredesignz 2009-08-22
 * @version 5.2.14

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */
//  @modify : dix3
//  @purpose : モジュール単位(コントローラ単位)での使用可能・不可能制御の組み込み
//  @link 
class MY_Router extends CI_Router {
  private $module;
  var $default_route = array();
  var $redirect_count = 0;
  public function fetch_module()
  {
    return $this -> module;
  }

  public function _validate_request( $segments )
  {
    if ( isset( $segments[0] ) && preg_match( '#^mdl_#iu', $segments[0] ) ) {
      // /mdl_ から始まるURLはルーティングの対象外とするようにした(URLで直接呼べないように)
      show_404( $this -> fetch_directory() . $segments[0] );
    }elseif ( isset( $segments[0] ) && !$this -> _route_active_check( $segments[0] ) ) {
      // モジュール単位(コントローラ単位)での使用可能・不可能制御
      if ( $this -> default_route && 0 === $this -> redirect_count ) {
        $this -> redirect_count++;
        return $this -> _validate_request( $this -> default_route );
      }else {
        show_404( $this -> fetch_directory() . $segments[0] );
      }
    }else {
      // end
      /**
       * locate the module controller
       */
      return $this -> locate( $segments );
    }
  }

  /**
   * * Locate the controller *
   */
  public function locate( $segments )
  {
    $this -> module = '';
    $this -> directory = '';

    /**
     * pad the segment array
     */
    $segments += array( $segments, NULL, NULL );
    list( $module, $directory, $controller ) = $segments;

    /**
     * module exists?
     */
    if ( $module AND is_dir( $source = MODBASE . $module . '/controllers/' ) ) {
      $this -> module = $module;
      $this -> directory = MODOFFSET . $module . '/controllers/';

      /**
       * module sub-controller exists?
       */
      if ( $directory AND is_file( $source . $directory . EXT ) ) {
        return array_slice( $segments, 1 );
      }

      /**
       * module sub-directory exists?
       */
      if ( $directory AND is_dir( $module_subdir = $source . $directory ) ) {
        $this -> directory .= $directory . '/';

        /**
         * module sub-directory sub-controller exists?
         */
        if ( $controller AND is_file( $module_subdir . '/' . $controller . EXT ) ) {
          return array_slice( $segments, 2 );
        }

        /**
         * module sub-directory controller exists?
         */
        if ( is_file( $module_subdir . '/' . $directory . EXT ) ) {
          return array_slice( $segments, 1 );
        }
      }

      /**
       * module controller exists?
       */
      if ( is_file( $source . $module . EXT ) ) {
        return $segments;
      }
    }

    /**
     * not a module controller
     */
    return parent :: _validate_request( $segments );
  } 
  // 呼び出したコントローラを使うかどうかのチェック
  function _route_active_check( $controller_name )
  {
    $ret = false;
    if ( defined( 'CRON' ) && CRON ) { // バッチ起動時は常にtrue
      return true;
    }
    if ( @include( APPPATH . 'config/route_active' . EXT ) ) {
      $route_active = ( ! isset( $route_active ) OR ! is_array( $route_active ) ) ? array() : $route_active;
      $active_group = isset( $active_group ) ? $active_group : "";
      if ( $active_group ) {
        $order_by = isset( $route_active[$active_group]['order_by'] ) ? $route_active[$active_group]['order_by'] : "deny";
        $allow_from = isset( $route_active[$active_group]['allow_from'] ) ? $route_active[$active_group]['allow_from'] : "";
        $deny_from = isset( $route_active[$active_group]['deny_from'] ) ? $route_active[$active_group]['deny_from'] : "";
        $this -> default_route = isset( $route_active[$active_group]['default_route'] ) ? $route_active[$active_group]['default_route'] : "";
        foreach( ( array ) $order_by as $v ) {
          foreach( ( array ) ${"{$v}_from"} as $v2){
            if("all" === strtolower($v2) || $controller_name === strtolower($v2)){
              if($v === "allow"){
                $ret = true;
              }
              if($v === "deny"){
                $ret = false;
              }
            }
          }
        }
      }
    }
    return $ret;
  }

}
/* End of file MY_Router.php */
/* Location: ./application/libraries/MY_Router.php */

 ?>


改造ソース4:application/libraries/MY_Loader.php
ci-cmsでのMatchbox→HMVCへの環境移し替え用。それ以外では本来不要。

<?php ( defined( 'BASEPATH' ) ) OR exit( 'No direct script access allowed' );
#ci-cms用、ci-cms以外ではこのファイルは不要
class MY_Loader extends CI_Loader {
  var $template = array();
  public function __construct()
  {
    $this -> template['module'] = '' ;
    parent :: __construct() ;
  }
} //endofclass

/* End of file MY_Loader.php */
/* Location: ./application/libraries/MY_Loader.php */


?>

動作確認

ci-cms上でHMVCが動いている例。
画面の中身はあまり気にしないでくださいw


pageモジュールのフロント側





pageモジュールの編集側





albumモジュールの管理側(albumモジュール内専用ライブラリ部分の呼び出し例)





モジュール内からの別モジュールのコントローラ直接呼び出しも動いている。




newsモジュール内のコントローラ内サブディレクトのコントローラ(application/modules/news/controllers/admin/settings.php)も呼べている。




ちなみにci-cms上のMatchBox→HMVCの入れ替えで
不要になるファイルは

  1. application/libraries/MY_Config.php
  2. application/libraries/MY_Language.php
  3. application/libraries/Router.php
  4. application/libraries/Loader.php
  5. application/libraries/Matchbox.php
  6. application/config/matchbox.php

の6ファイル。

必要になるファイルは上記で作った

  1. application/libraries/Modules.php
  2. application/libraries/Controller.php
  3. application/libraries/MY_Router.php
  4. application/libraries/MY_Loader.php

の4ファイル。





今日はここまで。ではでは!