CodeIgniterの学習 74 - dBugをCodeIgniter上で使いColdFusionのcfdump風に折りたたみ可能で見た目も綺麗なvar_dumpを表示出来るようにする

こんにちは!

今日はdBugをCodeIgniter上で使えるラッパーを作ってみる。


以前 のエントリー http://d.hatena.ne.jp/dix3/20081121/1227195464PEAR :: Var_Dumpを使ってプロファイラにvar_dump情報を整形して吐く俺俺ヘルパを作る )で、

既に見た目が綺麗なvar_dumpを実現していたが、今回はPEARを使わない版。


ColdFusionでお世話になっていたcfdumpのPHP版みたいなやつ。(さくっと画面が作れてColdFusionもいいよー)


dBugの紹介記事はこちらの中 - http://www.smashingmagazine.com/2009/01/20/50-extremely-useful-php-tools/
本家はこちら - http://dbug.ospinto.com/

かなり昔に気になってたのだが、開発の息抜きにCodeIgniterで使えるようにしてみた。

画面

こんな感じ。
適当なコントローラーとかで、

 dBug($this->form_validation);
 dBug($this->session);

みたいに呼び出すと、



てなかんじで、プロファイラ上にdBugの結果を表示する事が出来る。
各セルは折りたたみ可能。
呼び出し元のファイル名・行番号・メソッド名もついでに表示する様にしている。

見た目も綺麗で直感的で良い感じ。


設置方法

以下の通り

手順1:元ソースの取得
dBugを http://dbug.ospinto.com/ から ダウンロードして、dBug.php を application/libraries/dBug.phpとして設置する。



手順2:ワーニングが出ないように部分修正
application/libraries/dBug.phpの104行目付近

return $arrMatches[1];

return isset($arrMatches[1]) ? $arrMatches[1] : '';

に変更する。



手順3:dBugを呼び出すラッパーを作る

俺の開発環境では、
俺俺デバッグ関連用ヘルパーを、
application/helpers/debug_helper.php
として作り、

application/config/autoload.php内で、


$autoload['helper'] = array('registry','url',(..snip..),'cmn','log','debug');
みたいに呼び出すようにしているので、このdebug_helper.php内にラッパーファンクションを追加した。


application/helpers/debug_helper.phpの一部:

<?php

//snip

if ( ! function_exists( 'dBug' ) ) {
  // dBug wrapper ( see:http://dbug.ospinto.com)
  function dBug( $data )
  {
    $trace =  debug_backtrace();
    $fname = ( isset( $trace[0]['file'] ) ) ? $trace[0]['file'] : '';
    $line = ( isset( $trace[0]['line'] ) ) ? $trace[0]['line'] : '';
    $func = ( isset( $trace[1]['function'] ) ) ? $trace[1]['function'] : '';
    ob_start();
    new dBug($data);
    $output = ob_get_contents(); 
    ob_end_clean();
    $CI = &get_instance();
    if ( !isset( $CI -> dBug_data) || !is_array( $CI -> dBug_data) ) {
      $CI -> dBug_data= array();
    }
    $CI -> dBug_data['FILE:' . $fname . ' LINE:' . $line . ' FUNCTION:' . $func] = $output;
  }
}

//snip

?>

debug_backtraceで、呼び出し元情報を取得

$output に、dBugの結果を貯め込み

$CI -> dBug_data に保存している。

(後でプロファイラに渡せるように$CI内にdBug_dataを勝手に追加してる。)



手順4:application/libraries/MY_Profiler.php の改造

プロファイラの改造等々については、
以前のエントリー
http://d.hatena.ne.jp/dix3/20081016/1224124127 (CodeIgniterの学習 22 - 開発用のプロファイラを拡張し、db_sessionの情報を表示する)

http://d.hatena.ne.jp/dix3/20081005/1223161593 (CodeIgniterの学習 16 - 開発時にプロファイラを有効にし情報を表示する)
も参考にしてください。


(余談ですが、CodeIgniter1.7.0からは、CI標準のsessionライブラリで、db_sessionと同等の事が出来ます。たまにsession周りで誤解されているっぽいのを見かけるので一応。CI標準のSession.phpでクッキー保存方式・DB保存方式が選択できる。PHP標準の$_SESSION保存方式は別ライブラリが必要)



以下のソースでは、今回のdbug以外の改造部分(_compile_vd(),_compile_session()等)はとり除いています。
application/libraries/MY_Profiler.php

<?php if ( ! defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
class MY_Profiler extends CI_Profiler {
  function MY_Profiler()
  {
    parent :: CI_Profiler();
  }

  // dBug
  function _compile_dbug()
  {
    $output = "\n\n";
    if ( isset( $this -> CI -> dBug_data) && is_array( $this -> CI -> dBug_data) ) {      
      $output .= "\n";
      foreach( $this -> CI -> dBug_data as $k => $v ) {
        $output .= '<fieldset style="border:1px solid #339900;padding:6px 10px 10px 10px;margin:20px 0 20px 0;background-color:#eee">';
        $output .= '<legend style="color:#339900;">&nbsp;&nbsp;dBug('.$k.')&nbsp;&nbsp;</legend>';
        $output .= "\n";
        $output .= $v ;
        $output .= '</fieldset>';
        $output .= "\n";
      }
    }
    return $output;
  }

  function run()
  {
    $output = '<br clear="all" />';
    $output .= "<div style='background-color:#fff;padding:3px 10px;'>";
    #$output .= $this -> _compile_vd();
    $output .= $this -> _compile_dbug();// dBug
    $output .= $this -> _compile_uri_string();
    $output .= $this -> _compile_controller_info();
    $output .= $this -> _compile_memory_usage();
    $output .= $this -> _compile_benchmarks();
    #$output .= $this -> _compile_session();
    $output .= $this -> _compile_get();
    $output .= $this -> _compile_post();
    #$output .= $this -> _compile_cookie();
    $output .= $this -> _compile_queries();
    #$output .= $this -> _compile_queries_subdb();
    #$output .= $this -> _compile_misc();
    $output .= '</div>';
    return $output;
  }
}
// END CI_Profiler class
/**
 * End of file MY_Profiler.php
 */
/**
 * Location: ./application/libraries/MY_Profiler.php
 */

?>

ちなみに現在の環境は、CodeIgniter1.7.1です。





手順5:プロファイラを有効にしてdBugを動かしてみる

http://d.hatena.ne.jp/dix3/20081005/1223161593 の様な方法で開発時にプロファイラを有効にし、
任意のソース内で、

 dBug($this->form_validation);
 dBug($this->session);
 dBug($hoge);

等呼び出してみると、上の画面の様にダンプデータが視覚化される。


めでたしめでたし。


dBugは、PEAR::Var_dumpとは違って深くネストしたオブジェクトでも落ちないみたい。
お試しあれ。

Reply about the Scraping Example Test code.

This post is a reply to the shinokada's question.

Creating an object from a class in Codeigniter -
http://stackoverflow.com/questions/1513685/creating-an-object-from-a-class-in-codeigniter/


CodeIgniter study 14 (In Japanese text) -
http://d.hatena.ne.jp/dix3/20081002/1222899116

In English text (Google Translator) -
http://translate.google.com/translate?prev=hp&hl=en&js=y&u=http%3A%2F%2Fd.hatena.ne.jp%2Fdix3%2F20081002%2F1222899116&sl=ja&tl=en)

I've tried to post again. but I couldn't post with hyperlinks. sorry.. I'll answer to that in this site.(I'm a newbie stackoverflow.com :-( )


I think that I'll try to repost these answers after a few days .(on stackoverflow.com)


Answer

Hi! . This sample Scraping code was written based on using the library:
Snoopy - the PHP net client ( http://snoopy.sourceforge.net/ )

Mistakes in my English ,please pardon me : )


Q1. Am I correct to say that I can't use,

A1.

You have to install this source code:

  • step1: Download the base source(Snoopy-1.2.4.zip) at http://sourceforge.net/projects/snoopy/
  • step2: Unzip Snoopy-1.2.4
  • step3: Rename Snoopy.class.php to Snoopy.php (My sample Scraping libraly name is not 'Snoopy.php', 'Scraping.php' is correct)
  • step4: Move Snoopy.php(which was renamed) to application/libraries/Snoopy.php
  • step5: Install my sample wrapper code(class Scraping) to application/libraries/Scraping.php


Note1:
Required php-curl environment, I dont know how to install it on XAMPP.
This site may help you - http://stackoverflow.com/questions/176449/how-to-install-php-curl/ ,
Under the Linux OS, it's easy to install . (ex. yum install curl or apt-get install curl)


Note2:
If you want to try to use 'Snoopy class' only in HTTP ,Change the Snoopy.php at line 85. so Note1 is not required.

Snoopy.php (which was renamed) at line 85:

 #var $curl_path="/usr/local/bin/curl";
 var $curl_path=false;

Q2. Why do the author use

A2.

That's an "auto HtmlSpecialchars option".
Because the browser does not display the HTML tags.

(please see: http://www.php.net/manual/en/function.htmlspecialchars.php ).

Note3:
In the sample wrapper code, I use 'mb_convert_encoding' functions.
This php-function may not be installed on English environment.
(see: http://www.php.net/manual/en/function.mb-convert-encoding.php )

If you can't use this 'mb_ php-function' Change this. ..(or Install 'mb_' functions):

Scraping.php(my Snoopy Wrapper example code):

 #$str = mb_convert_encoding( (string) $this -> c -> results,"UTF-8","auto");
 $str = (string) $this -> c -> results ;


Q3. Could you explain APPPATH and EXT.

A3.

'APPPATH' and 'EXT' are Reserved Names(Constants) on CodeIgniter.

'APPPATH' - The full server path to the "system" folder. (ex. '/var/blah/blah/system/application/')

'EXT' - The file extension. Typically '.php'

(see:htdocs/index.php at line 88)
(see also: http://codeigniter.com/user_guide/general/reserved_names.html)




Regards. :-)

いそがしいにょ - 気になるリンク1

こんにちは!

たまに更新しないと寂しいのでちょこっと更新。
今CodeIgniter系で興味のあることのリンクだけ貼っておきます。


リンクなど

CodeIgniter - Taco Class Examples
http://findmeonthe.net/TACONITE/

CodeIgniter上でjqueryを使う方法の一つ。

http://www.malsup.com/jquery/taconite/ が必要。まだ試してない。
よさげだったら使ってみたい。

CodeIgniter + prototype.js + xajaxでDOM書き換えが楽すぎるが、軽いならつかってみようかなって感じ。



Flexigrid Implemented in CodeIgniter
http://flexigrid.eyeviewdesign.com/

CodeIgniter + flexigrid ( http://www.flexigrid.info )
(テーブルをajax経由で操作、ページネーションとか絞り込みとか並び替えとか)
実験済み
ほぼサンプルソース通りで動く。
サンプル+$this -> load -> config( 'flexigrid');を追記する必要有り



A full real CI example website (see story inside) | CodeIgniter Forums
http://codeigniter.com/forums/viewthread/129017

ここをたどっていくと、
音楽の歌詞系ポータルサイトを作った方が、全ソースを公開してくれています。
稼働中のサイトもこのリンクからたどれる。公開するに至った事情がおありのようです。

事情の超意訳:
公開しているデータ(歌詞)の著作権絡みでサイトを諦めるとかどうとか、でも折角作ったソース(CodeIgniter)は公開してCIユーザーの何かの助けになれば等々。


ソースダウンロード済み、ぱっと見ただけ。

かなりのソース量+作り込みなので、
勉強・研究で一通りの仕組みがそろったサンプルソースを探している人は
ちょっと参考になるかも。



Simple & EASY PHP CMS Built With Codeigniter - Codefight.org
http://www.codefight.org/cms-preview/cms-codefight-built-with-codeigniter-preview-demo-code.html

CodeIgniter製のCMS 多分オーストラリア製
ソースダウンロード済み、中身見てない。
今現在開発がアクティブっぽい。作っているかたがやる気モード中か。(最新版は数日前にアップされている。)
今度試す。

コメント残してくれって書いているけどガラスのハートな俺はコメント書けない><

ぱっと見よさ気なんですが。
目移りするのは俺が浮気性だからかwいや新しもの好きなだけだ。




来月は何回か更新したいっす。
忙しいときは気になるリンク続編とかで。
ではでは!

CodeIgniterの学習 73 - 標準のValidation機能を拡張し、Javascriptとphpの両方でバリデーションチェックしてみるその2(Form_validation化対応コード、暫定版)

こんにちは!

今日は以前のエントリー

http://d.hatena.ne.jp/dix3/20081013/1223862786 -
CodeIgniterの学習 21 - 標準のValidation機能を拡張し、Javascriptphpの両方でバリデーションチェックしてみる(ClientServer Validation の改造)


で俺俺改造した、ClientServer Validation の Form_validation 化版コードを貼っておく。
説明しだすと長いので、今日は画面ダンプと、ライブラリーのコードと、ポイントのみ。
使用例とかは次回のエントリーにでも。


以前のエントリー記述時のCodeIgniter(1.6系)では、Form_validationライブラリは存在せず、Validationライブラリのみが存在していた。
ValidationライブラリはCI 1.7.1でも存在するが、Validationライブラリは過去のものとなり、Form_validationの使用が推奨されていたはず。

以前の俺のエントリーも過去のものに化しているので、俺俺改造ソースもForm_validation上で何となく動くように更新。


画面


こんな感じ。POST前にjavascriptで必須チェック等を実行する。

簡易バリデーションチェックをクライアント側で、厳密なバリデーションチェックをサーバ側で行う二段構え。




コード

こんな感じ。一応動くけどテストが足りない。故に暫定版。

但し、<input name='hoge[]' /> みたいに配列で渡したときに動くかは未テスト。
テストが足りないコードを貼るなよというツッコミには、お好きにいじくり回して改造してください。・改良したらまた貼るよ。という事で逃げておく。


ちなみに元ネタは、http://codeigniter.com/wiki/ClientServer_Validation/ です。かなり変わっているけど。


ライブラリ:application/libraries/MY_Form_validation.php

<?php if ( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' );
// 日本語 wikiから
// ------------------------------------------------------------------------
/**
 * Validation Class extension
 *
 * @package CodeIgniter
 * @subpackage Libraries
 * @category Validation
 * @author AK
 * @link http://codeigniter.com/user_guide/libraries/validation.html
 */

/**
 * modified : http://d.hatena.ne.jp/dix3 at 2008/10/13
 * link : http://d.hatena.ne.jp/dix3/20081013/1223862786
 */
/**
 * modified : http://d.hatena.ne.jp/dix3 at 2009/09/12
 * link : http://d.hatena.ne.jp/dix3/20090912
 */

class MY_Form_validation extends CI_Form_validation {
  private $_tmp_field_data = array();
  private $_defaultbgcolor = '#fff'; //input領域の背景色
  private $_alertbgcolor = '#ffbbcc'; //アラートの背景色
  public function __construct()
  {
    parent :: __construct();
  }

  public function set_rules( $field, $label = '', $rules = '' )
  {

    // If an array was passed via the first parameter instead of indidual string
    // values we cycle through it and recursively call this function.
    if ( is_array( $field ) ) {
      foreach ( $field as $row ) {
        // Houston, we have a problem...
        if ( ! isset( $row['field'] ) OR ! isset( $row['rules'] ) ) {
          continue;
        }
        // If the field label wasn't passed we use the field name
        $label = ( ! isset( $row['label'] ) ) ? $row['field'] : $row['label'];
        // Here we go!
        $this -> set_rules( $row['field'], $label, $row['rules'] );
      }
      return;
    }
    // No fields? Nothing to do...
    if ( ! is_string( $field ) OR ! is_string( $rules ) OR $field == '' ) {
      return;
    }
    // If the field label wasn't passed we use the field name
    $label = ( $label == '' ) ? $field : $label;
    // Is the field name an array?  We test for the existence of a bracket "[" in
    // the field name to determine this.  If it is an array, we break it apart
    // into its components so that we can fetch the corresponding POST data later
    if ( strpos( $field, '[' ) !== FALSE AND preg_match_all( '/\[(.*?)\]/', $field, $matches ) ) {
      // Note: Due to a bug in current() that affects some versions
      // of PHP we can not pass function call directly into it
      $x = explode( '[', $field );
      $indexes[] = current( $x );

      for ( $i = 0; $i < count( $matches['0'] ); $i++ ) {
        if ( $matches['1'][$i] != '' ) {
          $indexes[] = $matches['1'][$i];
        }
      }

      $is_array = TRUE;
    }else {
      $indexes = array();
      $is_array = FALSE;
    }
    // Build our master array
    $this -> _tmp_field_data[$field] = array( 'field' => $field,
      'label' => $label,
      'rules' => $rules,
      'is_array' => $is_array,
      'keys' => $indexes,
      'postdata' => NULL,
      'error' => ''
      );
    parent :: set_rules( $field, $label, $rules);
  }

  /**
   * JavaScript
   *
   * This function provides default implementation of the built-in CI validation rules in javascript.
   * The function generates a client-side js script, complete with <script>...</script> html tags,
   * suitable for inclusion in the document header. Additionally, custom rules can be added by
   * defining global js functions, or extending Validation js object.
   *
   * @access public
   * @param string $ - custom error message (optional)
   * @param string $ - name of a js error callback function (optional)
   * @return string - js
   */

  public function javascript( $alert_msg = '', $alert_func = 'null' )
  {
    if ( !$this -> _tmp_field_data ) {
      return $this -> _get_js_null_validation_run();
    }
    // client-side javascript implementation of CI built-in validation rules.
    // default alert message
    if ( $alert_msg == '' ) {
      $alert_msg = addslashes( 'Please fix the following errors:' );
    }
    // Load the language file containing error messages
    $this -> CI -> lang -> load( 'form_validation' );
    $params = null;
    foreach ( $this -> _tmp_field_data as $field => $v ) {
      // Explode out the rules!
      $ex = explode( '|', $v['rules'] );
      $messages = array();

      foreach ( $ex as $rule ) {
        $param = false;
        if ( preg_match( "/(.*?)\[(.*?)\]/", $rule, $match ) ) {
          $rule = $match[1];
          $param = $match[2];
        }
        if ( ! isset( $this -> _error_messages[$rule] ) ) {
          if ( false === ( $line = $this -> CI -> lang -> line( $rule ) ) ) {
            // エラー文言が見つからないものはとりあえずメッセージは空白にしておく
            // $line = 'Unable to access an error message corresponding to your field name.';
            //$line = '';
            // エラー文言が見つからないものは処理素通り
            continue;

          }
        }else {
          $line = $this -> _error_messages[$rule];
        }
        // Build the error message
        $mparam = isset( $v['field'] ) ? $v['field'] : $field ;
        $mfield = isset( $v['label'] ) ? $v['label'] : $v['label'];
        $messages[] = sprintf( $line, $mfield, $mparam );

      }
      if(!$messages){
        continue;
      }
      $params[] = '{input:"' . $field . '",rule:"' . $v['rules'] . '",name:"' . ( isset( $v['field'] ) ? addslashes( $v['field'] ) : $field ) . '",msg:"' . join( ' ', $messages ) . '"}';
    }
    $join_params = join( ",\n\t\t", $params ) ;
    return $this -> _get_js_validation_run( $this -> _defaultbgcolor, $this -> _alertbgcolor, $alert_msg, $join_params, $alert_func );
  }
  // ------------------------------------------
  private function _get_js_null_validation_run()
  {
    $script = <<<___END___
<script type="text/javascript">
// <![CDATA[
var validation_run = function(f) {}
// ]]>
</script>

___END___;
    return $script;
  }
  // ------------------------------------------
  private function _get_js_validation_run( $defaultbgcolor, $alertbgcolor, $alert_msg, $join_params, $alert_func )
  {
    $script = <<<___END___
<script type="text/javascript">
// <![CDATA[
String.prototype.trim = function() { return this.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); }
var Validator = function(f) { this.form = f; }
Validator.prototype.required = function(str) { return str.search(/\S/) > -1; }
Validator.prototype.matches = function(str, field) { return (str == this.form.elements[field].value); } // FIX! change "field" from input name to input ref?
Validator.prototype.max_length = function(str, val) { return (str.length <= val); }
Validator.prototype.min_length = function(str, val) { return (str.length >= val); }
Validator.prototype.exact_length = function(str, val) { return (str.length == val); }
Validator.prototype.valid_email = function(str) { return str.search(/^([\w\+\-]+)(\.[\w\+\-]+)*@([a-z\d\-]+\.)+[a-z]{2,6}$/i) > -1; }
Validator.prototype.valid_ip = function(ip) { var segments = ip.split("."); for (var i in segs) if(segs[i].length>3 || segs[i]>255 || segs[i].search(/\D/)>-1) return false; return true; }
Validator.prototype.alpha = function(str) { return str.search(/[^a-z]/i) == -1; }
Validator.prototype.alpha_numeric = function(str) { return str.search(/[^a-z0-9]/i) == -1; }
Validator.prototype.alpha_dash = function(str) { return str.search(/[^\w-]/i) == -1; }
Validator.prototype.numeric = function(str) { return ! isNaN(str); }
Validator.prototype.integer = function(str) { return ! (isNaN(str) || str.indexOf(".") > -1); }
Validator.prototype.valid_base64 = function(str) { return str.search(/[^a-zA-Z0-9\/\+=]/) == -1; }
Validator.prototype.validate = function (rules, callback) {
  try {
    if (!rules.length) return true;
    var res, errors=[];
    var disperr = false;//個別エラー表示用フラグ追加
    var focus_flg = false;
    for (var i in rules) {
        var item = rules[i];
        var field = this.form.elements[item.input];
        var rule_list = item.rule.split("|");
        var required = false;//必須かどうかのフラグ
        for (var r in rule_list) {
            required = (item.rule.search(/required/) == -1) ? required : true;
            if(rule_list[r].search(/trim/)){
                //trimの時は画面の値をまずtrimして判定する
                field.value = field.value.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
            }
            //任意項目かどうかのフラグ追加
            var re = /(callback_|validate_)?(\w+)(?:\[(.+)\])?/i.exec(rule_list[r]);
            var func = re[2];
            if (!this[func]) {
                //try { func = eval(func); } catch (e2) {  }
                //javascript側で未知のファンクション(xss_clean 等があるときは、クライアントサイド側では無視する。
                //サーバーサイド側でもう一度走るのであまり問題ないはず。
                try{
                    func = eval(func);
                    res = (typeof(func) == "function") ? func(field.value, re[3]) : false;
                }catch(e2){}
            } else {
                res = this[func](field.value, re[3]);
            }
            //任意項目で且つ空白の時はエラー文言は表示しない
            if( (false == required) && ( "" ==field.value) &&  ( res == false ) ){
                res = true;
            }
        }
        var obj_input = document.getElementById( item.input );
        var obj = document.getElementById( item.input + "_error" );
        if (!res && item.msg) {
            if(obj){//もし id="input項目_error" が存在しているならば、そこに個別にエラー表示する
                obj.innerHTML = item.msg ;
                if(obj_input){//もし入力項目に同名のidが振っているならば、背景色をピンク色にする
                    obj_input.style.background="{$alertbgcolor}";
                    if(!focus_flg){
                      obj_input.focus();
                      focus_flg = true;
                    }
                }
                disperr = true;
                errors.push([item.msg, item.input]);
            }else{
                //元ソースではこっち
                errors.push([item.msg, item.input]);
            }
        }else{
            if(obj){
                obj.innerHTML = "" ;
            }
            if(obj_input){
                obj_input.style.background="{$defaultbgcolor}";
            }
        }
    }
  } catch (e) {
    alert(e);
    return false;
  }
  if (errors.length) {
    // show errors
    if(disperr){//個別エラー表示時
      //display_alert(errors);//画面上部にも同時にエラーを出したい時
      return callback ? callback(errors) : false ;
    }else{
      return callback ? callback(errors) : display_alert(errors);
    }
  }
  return true;
}
var display_alert = function(errors) {
  var str = '';
  var obj = document.getElementById('error_string');
  //もしid="error_string" が存在しているときはこのinnerHTML上に出す、それ以外はアラートで出す
  if(obj){
    for (var i in errors){
      str += '<br>'+ errors[i][0];
      obj.style.display = 'block';
      obj.innerHTML = '{$alert_msg}' + str ;
    }

  }else{
    for (var i in errors){
      str += "\\n- " + errors[i][0];
      window.alert('{$alert_msg}' + str);
    }

  }
  return false;
}
var  validation_run = function(f) {
  var rules = [{$join_params}];
  return new Validator(f).validate(rules,{$alert_func});
}
// ]]>
</script>

___END___;

    return $script;
    # 元々はこっち
    // default implementation of the validation action
    // Original display_alert
    /**
     * $script .= '
     * function display_alert(errors) {
     * var str = "";
     * for (var i in errors)
     * str += "\n- " + errors[i][0];
     * alert("' . addslashes( $alert_msg ) . '" + str);
     * return false;
     * }
     * ';
     */

  }
} //endofclass
/**
 * End of file MY_Form_validation.php
 */
/**
 * Location: ./application/libraries/MY_Form_validation.php
 */
?>


使い方

次回に書く予定。今日はポイントのみ。


1)適当なコントローラとかでform_validationライブラリをloadした後、

2)コントローラ内等で

<?php
//上略
// 編集画面のバリデーション
$this -> form_validation -> set_rules( 'tmp_name' , 'テンプレート名', 'trim|required|xss_clean' );
$this -> form_validation -> set_rules( 'tmp_body' , 'テンプレート', 'trim|required' );
$this -> form_validation -> set_rules( 'memo' , 'メモ', 'trim|xss_clean' );
//下略
?>

みたいにルールの設定をする。(ココまではマニュアル通り。)
このフォームバリデーションの設定に従って、javascript側の簡易バリデーションチェック用ソースを作ってくれるわけです。



3)上のルールを設定した後にビュー側に、

<?php
//上略
$data['validation_js'] = $this -> form_validation -> javascript() ;
//下略
?>

とかで、今回のライブラリ拡張で作られるjavascriptを生成して渡してあげる。


4)ビューのhead部分に、

<?php if(isset($validation_js)){echo $validation_js ;} ?>

とかで3)で生成したjavascriptを貼りつける。


5)ビュー側のsubmitボタンを、

<input type="submit" onclick="javascript:try{if(!validation_run(this.form)){return false;}}catch(e){window.alert(e);return false;}" name="submit" value="保存" />

みたいに、onclick時に、validation_run(this.form)
を呼んで、falseならPOSTしないようにする。


6)ビュー側の各inputタグ周りを、

<label>テンプレート名<em>(必須)</em></label>
<span id="tmp_name_error"><?=form_error('tmp_name')?></span>
<input type="text" name="tmp_name" id="tmp_name" value="<?=set_value('tmp_name',$tmp_name) ?>" size="60" />

みたいにする。


エラー表示文字列部分を、


id="hogehoge_error"
とidを付けて囲い、

inputタグ部分を、


type="text" name="hogehoge" id="hogehoge"
とname,idを付けるところがキモ。

あとは、Form_validationのマニュアル通り、

  • エラー表示部分は、form_error
  • value部分は、set_value

を使う。





今日はここまで。


いろいろやらなくてはならないことが沢山あって忙しいです。
OpenCartをCodeIgniterに移植して、日本の商慣習に合わせたいとか、やりたいことも色々。

CodeIgniterにJobboardを組み込む5-ci-cmsのモジュール構造

こんにちは!
Jobboard案件が後ろに延びて(というか他の横入り案件を先にやらなくてはならなくて)、こっちの方はちんたら進めています。


今日はci-cmsのモジュール構造を簡単に書こうと思います。

コピペして後はサクサクモジュール作っていきまっしょい。


既に独自に構築してしまっている方には不要なエントリーです。
どっちかというとこれから手早く始めたい人向け。


モジュールのツリー構造

ここでは、モジュール名を mtest と仮定します。
基本のCodeIgniterとさして変わりません。


application/modules/以下のtree例
横に長いですが、スクロールして見てちょ

-- mtest … モジュール名
-- controllers
-- admin … 1)管理画面用サブディレクト
`-- admin.php2)管理画面用コントローラ
-- index.html … ダミーファイル
`-- mtest.php3)モジュール用コントローラ ttp://example.com/mtest/ (hoge.phpとかも可、この時は ttp://example.com/mtest/hoge
-- locale … 4)言語ファイル用ディレクトリ(ja.po,fr.poとか、Poeditで編集する)
-- ja.mo
`-- ja.po … 5)日本語用翻訳ファイル
-- models
-- index.html … ダミーファイル
`-- mtest_model.php … 6)モデル、ファイル名は何でも良いが
-- mtest_blocks.php7)別のモデルのView等で、$this->block->get('hogehoge', 引数))の様に使って、データまたは文字列(HTMLタグ) を動的に生成するときに使う
-- mtest_install.php8)モジュールインストール時の挙動をココに書く、mkdirで専用ディレクトリの作成とか、テーブルの作成とか、ci_settings テーブルにモジュール個別設定値保存用行を追加とか泥臭いことをベタベタと。
-- mtest_plugins.php9)モジュール間をまたがった追加の処理などを行うときに使う。元ソースを見るとWordPressのadd_filterを移植しているみたい。使い方も多分一緒。
-- mtest_routes.php10)モジュール内のルーティング設定はココに書く。
-- mtest_update.php11)モジュールのバージョンアップ時の処理用ファイル、アップデートスクリプトなどを書く。モジュールのバージョンを上げていくときの過去に作成したデータとのつじつま合わせをここでベタベタと。
-- setup.xml12)ci-cmsのモジュール管理(インストール)用定義ファイル
`-- views
-- admin … 13)管理画面用ビューのサブディレクト
-- admin.php14)管理画面用ビューファイル、サブディレクトリには入れてないみたい。入れても動くでしょ、多分
-- blocks … 15)ブロック画面用ビューファイル保存用ディレクトリ。7)の処理のビュー保存用(この例ではブロック無し)
-- index.html … ダミーファイル
`-- index.php … … 16) 3)のビューファイル、index.phpじゃなくて、mtest_view.phpとかで良いかと、むしろそっちの方がわかりやすい。

という感じ。


ファイル名の強制は、

  • 2)と3) … ただし例外もあり
  • 7)から11)

みたいです。


構造がわかれば後は、ある程度お膳立ての揃ったCodeIgniterの世界。


ゼロから作るのは大変なので一足飛びでさくさくいきましょー。
このエントリーはゆっくりなので、ダウンロードしてコード読んだ方が速いともいうw


不満な所は俺俺改造して俺色に染める感じで。

ちなみにコードを汚すのは嫌という強迫観念をお持ちの方は(俺もか)、
コードを汚すのではなく自分が鍛え上げているんだ!と言い換えてみませう。
Google様だってmysqlハックしてんじゃんとか言い聞かせながら。



おまけ

ci-cmsに過去ソースの適当なモジュールを移植して動かしてみた図。
まあすんなり移植できた。元々複雑には作らないからかもしれないが。


画面じゃわかんないってw





今日はここまで。

次回は上記の 1)〜16)の中身とポイントをかいつまんでいく予定だったりそうじゃなかったり、気まぐれです。


ではでは!

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ファイル。





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

CodeIgniterにJobboardを組み込む4-モックを作ってやる気を鼓舞してみる

こんにちは
お盆なのに何故か仕事をしています。(仕事なのか遊びなのか)


今日はJobboardの画面構成というか、ラフイメージを書いてみます。(実物とは変わる可能性大)



とりあえず雰囲気だけでも出してみない事には、
例え仕事でも全くやる気が起きない・拡張ポイントに気づかないので、
いつも先にある程度動かせる形から入ります。


エクセル紙芝居(orパワポ)でもいいけど、動かない絵だと説明がしにくい&説明時に改善ポイントのツッコミの声が出にくいので、私はモックを作る方を好みます。
手で触れて実際に動かせるものの説得力は格段に違います。


「もう出来てるじゃん」「これならすぐ出来るね」「じゃあ2週間で」
って言われないようにしないと自分の首を絞めるけどw


画面はこんな感じ

前回のエントリーで書いた
「Web App Theme」( http://github.com/pilu/web-app-theme/tree/master , 元ネタ解説: http://www.moongift.jp/2009/06/web_app_theme/
を、ci-cmsのテーマとして移植し少し改造して、

ロゴは FREE Logo Maker( http://www.logomaker.com/ )で暫定的に作ってみました。


メインブロック部分は、jobberBaseの画面ダンプを取りあえず貼っただけ。
ここが動くようになる感じです。(iframeを使うわけではない)


しっとりツヤツヤ系の作りじゃない・写真・アイコン・バナーが無い、生きたサイトな感じがまだしないですが、モックなのでまあ良いでしょう。
個人的にはサイトが丸っこい・鏡面仕上げなのはあまり好きじゃないです。


(09/08/28追記)
管理画面のブルー基調も古い感じがして気に入らなかったので、
画像をグレースケール変換+ガンマ補正+ちょっとだけcssを弄って雰囲気を変えてみた。


色変更でちょっとはマシになったかな。
日本語化はしてないけど、そのへんは後でja.poファイルを弄ればいいので後回し。
(09/08/28追記おわり)



作成方針:ほぼ決定

  1. 外枠・静的コンテンツ・特集部分はci-cms(CodeIgniter)で作る。(但しWYSIWYGでは作らずにHTMLタグ貼り付けにする。)
  2. 真ん中部分のJobboard部分は、jobberBase - http://www.jobberbase.com/をモジュール化&改造して組み込む。
  3. 右サイドバー部分のブロック部分(新着等)は、jobberBaseのテーブルから適当に読み出して表示する。
  4. 仕事を探す側はログイン・会員登録は不要
  5. 求人掲載をする側は、要会員登録(といっても今回は実は掲載をする側は一社又は数社が元締めになる筈なので作らない可能性大)
  6. 裏画面(システム管理画面)は、ci-cmsの管理画面を流用
  7. 携帯向けテンプレは後で考える。
  8. その他のブロック部分(バナー管理・スポンサー・広告管理等)は余裕があるorやる気があるなら、専用モジュールを作って呼び出せるようにする。


という方向で実現出来ることを確信して今日はここまで。


ではでは!