แสดงบทความที่มีป้ายกำกับ zend แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ zend แสดงบทความทั้งหมด

วันศุกร์ที่ 11 กุมภาพันธ์ พ.ศ. 2554

CodeIgniter with Multiple Database Support

Over the last few months I have been using the CodeIgniter framework for several projects. So far, I have been very pleased with its flexibility and even more so with its extensibility.
On one my projects, I needed to connect to multiple databases at the same time. CI easily supports this via configuration settings, however the built in profiler only supports the default database. No problem, let’s extend it!

Step 1: Edit database.php and use descriptive group names.

In this tutorial, I am connecting to 2 separate databases. So far, this is nothing new, however your settings need to be correct.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$active_group = "default";
$active_record = TRUE;
 
$db['default']['hostname'] = "";
$db['default']['username'] = "";
$db['default']['password'] = "";
$db['default']['database'] = "";
$db['default']['dbdriver'] = "mysql";
$db['default']['dbprefix'] = "";
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE;
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = "";
$db['default']['char_set'] = "utf8";
$db['default']['dbcollat'] = "utf8_general_ci";
 
// add alternate database settings by gotphp.com
 
$db['alternate']['hostname'] = "";
$db['alternate']['username'] = "";
$db['alternate']['password'] = "";
$db['alternate']['database'] = "";
$db['alternate']['dbdriver'] = "mysql";
$db['alternate']['dbprefix'] = "";
$db['alternate']['pconnect'] = TRUE;
$db['alternate']['db_debug'] = TRUE;
$db['alternate']['cache_on'] = FALSE;
$db['alternate']['cachedir'] = "";
$db['alternate']['char_set'] = "utf8";
$db['alternate']['dbcollat'] = "utf8_general_ci";

Step 2: Edit autoload.php and autoload ALL models.

This is needed so the profiler is aware of the other databases and can iterate accordingly.
IMPORTANT: If you ONLY load your models on demand, that’s OK too. You still want to follow this step, but simply comment out the $autoload variable when you are NOT in debug mode. :)
Example:
1
2
3
4
$autoload['model'] = array(
    'alternate_model'// loaded by gotphp.com
    'default_model',    // loaded by gotphp.com
);

Step 3: In your models, use the actual database group names for your db connection by defining $db_group_name.

In other words, stop using $this->db and start using $this->[group], etc.
IMPORTANT: Be sure to establish a connection to the database group in the model’s constructor AND add a new method to get the database group for this mode. THIS IS REQUIRED.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Default_model extends Model {
 
    /**
     */
    var $db_group_name = "default";
 
    // --------------------------------------------------------------------
 
    /**
     *  Constructor -- Loads parent class
     */
    function __construct()
    {
        parent::__construct();
 
        $this->{$this->db_group_name} = $this->load->database($this->db_group_name, TRUE);
 
    }
 
    // --------------------------------------------------------------------
 
    /**
     *  Required method to get the database group for THIS model
     */
    function get_database_group() {
        return $this->db_group_name;
    }
 
    // --------------------------------------------------------------------
 
}
Now in EACH method in THIS model, use $this->{$this->db_group_name}.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Default_model extends Model {
 
    // --------------------------------------------------------------------
 
    function get_example_data()
    {
        $query = $this->{$this->db_group_name}->get('default_example');
        return $query->result_array();
    }
 
    // --------------------------------------------------------------------
 
}

Step 4: Extend CI’s profiler class to include all databases in the debug output

In your config.php file, take note of your subclass_prefix setting. You will need to use this in order to extend CI’s core classes automatically.
Example:
1
$config['subclass_prefix'] = 'MY_';
Now, create a new file in your application’s libraries directory called MY_Profiler.php.
IMPORTANT: If you changed your subclass_prefix, replace “MY_” in the rest of this tutorial with your custom setting.
In this new file, you will be extending the CI_Profiler, defining your own run method to account for multiple database groups, and adding a new display method that shows the database group AND model for each query!
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class MY_Profiler extends CI_Profiler {
 
    /**
     * Compile Multiple Database Queries
     * @return  string
     */
    function _compile_multi_db_queries($database, $model)
    {
        ... too much to copy here ... download file to review ...
    }
 
    // --------------------------------------------------------------------
 
    /**
     * Run the Profiler
     *
     * @access  private
     * @return  string
     */
    function run()
    {
        $output = "
<div id="codeigniter_profiler" style="clear: both; background-color: rgb(255, 255, 255); padding: 10px;">";
 
        if ($this->CI->config->item('show_uri_string')) {      $output .= $this->_compile_uri_string(); }
        if ($this->CI->config->item('show_controller_info')) {     $output .= $this->_compile_controller_info(); }
        if ($this->CI->config->item('show_memory_usage')) {    $output .= $this->_compile_memory_usage(); }
        if ($this->CI->config->item('show_benchmarks')) {      $output .= $this->_compile_benchmarks(); }
        if ($this->CI->config->item('show_cookies')) {             $output .= $this->_compile_variables('cookie_vars'); }
        if ($this->CI->config->item('show_get_vars')) {        $output .= $this->_compile_variables('get_vars'); }
        if ($this->CI->config->item('show_post_vars')) {       $output .= $this->_compile_variables('post_vars'); }
        if ($this->CI->config->item('show_uri_vars')) {        $output .= $this->_compile_variables('uri_vars'); }
        if ($this->CI->config->item('show_tpl_vars')) {        $output .= $this->_compile_variables('tpl_vars'); }
        if ($this->CI->config->item('show_session_userdata')) { $output .= $this->_compile_variables('session_userdata'); }
 
        if ($this->CI->config->item('show_db_multi_queries')) {
 
            // Include the autoload config to access the array of models in this app.
            include(APPPATH.'config/autoload'.EXT);
 
            // Loop through each model to set the database object
            foreach($autoload['model'] as $model) {
 
                // Define the database object name
                $database = $this->CI->$model->get_database_group();
 
                // Compile the output
                $output .= $this->_compile_multi_db_queries($database, $model);
 
            }
 
        } else {
 
            $output .= $this->_compile_queries();       
 
        }
 
        $output .= '</div>
 
';
 
        return $output;
    }
 
    // --------------------------------------------------------------------
 
}

Step 5: You will need to enable the built in profiler in one of your controllers and set the new configuration paramaters in a profile.php config file.

Example (enable profiler):
1
2
3
4
5
// Load the profile.php config file if it exists
$this->config->load('profiler', false, true);
if ($this->config->config['enable_profiler']) {
    $this->output->enable_profiler(TRUE);
}
Example (profiler.php file):
1
2
3
4
5
6
7
8
9
10
11
12
$config['enable_profiler']          = 1;
$config['show_uri_string']          = 1;
$config['show_controller_info']     = 1;
$config['show_memory_usage']        = 1;
$config['show_benchmarks']          = 1;
$config['show_cookies']             = 1;
$config['show_get_vars']            = 1;
$config['show_post_vars']           = 1;
$config['show_uri_vars']            = 1;
$config['show_tpl_vars']            = 1;
$config['show_session_userdata']    = 1;
$config['show_db_multi_queries']    = 1; // Only enable if you need to show more than one database in the profiler
If you have followed the steps above in order, your CodeIgniter application can now connect to multiple databases at the same time and each connections’ queries will display in the profiler like this:

วันพฤหัสบดีที่ 10 กุมภาพันธ์ พ.ศ. 2554

Code Igniter use muti database 1

Sometimes you might fall in a situation where you need multiple database connection at the same time. For example you might want to move (insert and delete) data from one database into another. This feature is very much useful for news sites as well due to maintain enourmass data.
Connection with multiple database with CI(Code Igniter) need a small trick. First you have to add lines of code in your configuration file in application/config/database.php. Add as much database configuration as you need
$active_group = "default";
$active_record = TRUE;

$db['default']['hostname'] = "localhost";
$db['default']['username'] = "root";
$db['default']['password'] = "";
$db['default']['database'] = "ci_practice";
$db['default']['dbdriver'] = "mysql";
$db['default']['dbprefix'] = "";
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE;
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = "";
$db['default']['char_set'] = "utf8";
$db['default']['dbcollat'] = "utf8_general_ci";



$db['second_db']['hostname'] = "localhost";
$db['second_db']['username'] = "root";
$db['second_db']['password'] = "";
$db['second_db']['database'] = "dev_job";
$db['second_db']['dbdriver'] = "mysql";
$db['second_db']['dbprefix'] = "";
$db['second_db']['pconnect'] = FALSE;
$db['second_db']['db_debug'] = TRUE;
$db['second_db']['cache_on'] = FALSE;
$db['second_db']['cachedir'] = "";
$db['second_db']['char_set'] = "utf8";
$db['second_db']['dbcollat'] = "utf8_general_ci";

So you have configured two different databases with their hostname, username and password. Moreover, you named them as default and second_db. In addition, you can choose any you like and as much database as you wish.
Now your class code application/controllers.test.php.
class Test extends Controller {
function __constructor()
{
parent::Controller();
$this->load->helper('url');
}
function duel_db2()
{
$this->load->model('test_model');
$user_data = $this->test_model->get_country(1);
print_r($user_data);


$user_data = $this->test_model->get_feedback(1);
print_r($user_data);
$this->load->database('default'); //Get Back into default.
}
}

finally your model (application/test_model.php) where from actually you connect multiple databases.
function get_country()
{
$DBOne = $this->load->database('second_db', TRUE);
$query = $DBOne
->from('countries')
->limit(10)
->where('id' , 10)
->get();
if ($query->num_rows() > 0)
{
$rows = $query->row_array();
return $rows;
}
return false;
}


function get_feedback($id)
{
$DBTwo = $this->load->database('default', TRUE);
$query = $DBTwo
->from('feedback')
->limit(10)
->where('id' , $id)
->get();
if ($query->num_rows() > 0)
{
$rows = $query->row_array();
return $rows;
}
return false;
}

Finally how it works? Lets say you call test controller as www.your-site.com/test/duel_db2
the duel_db2() function call test_model twice. first get_country(1). Lets have a closer look in this function. You connect default database by
$this->load->database('default');
and gets data from first database(ci_practice here). Second function call get_feedback() let the system able to connect with second database(dev_job here) by $DBTwo = $this->load->database('default', TRUE);
Keep in mind both of your connection is still alive :)
Enjoy with mutiple database connection simultaniouly with excellent framework (CI).

วันพฤหัสบดีที่ 27 มกราคม พ.ศ. 2554

ACL CL with Zend

เอาล่ะครับ มาต่อกันจากปีที่แล้ว 555+

ช่วงนี้ผมไปวุ่นๆ กับไอ้ Blogs ตัวนี้ จนไม่ได้มาอัพเดทอะไรเลย แต่วันนี้พอจะมีเวลาเหลือสักหน่อยหลังจากทำเรื่อง Order เสร็จก็จะมาเล่าให้ฟังถึงวิธีการทำ Controller ให้สมบูรณ์ยิ่งขึ้น นั่นก็คือ Controller ที่ใช้ใน Engine นี้แหละ

ก่อน อื่นต้องมาทำความเข้าใขกับตัว C ใน MVC เสียก่อน ตัวมันเองนั้นถือว่าเป็นหหัวใจเลย เพราะมันเป็นทั้งตัวเชื่อมต่อ models กับ views เข้าหากัน และยังเป็นตัวทำงาน Login สำคัญๆ เช่น validation ก่อน สั่งให้ไปหน้านู้น หน้านี้ ถือว่าเป็นแกนหลัก ใน MVC เลยทีเดียว

ตัว Controller ของ CI นั้นจริงๆแล้วก็พอจะมีความสามารถอยู่ในระดับนึง ทำงานค่อนข้างเร็วและมีประสิทธิภาพ แต่ที่ขาดไป และเป็นหัวใจเลยนั่นก็คือ ACL (Access Controller List) แล้ว CI ก็ไม่ได้เตรียม lib ในส่วนนี้มาให้เราเสียด้วย เราก็เลยต้องมาเหนื่อยหน่อย แต่ทำทีเดียวจบครับ....

ก่อนอื่นเราก็ต้องมาสร้าง MY_Controller ขึ้นมา โดยเอาใส่ไว้ใน application/libraries/

ซึ่ง method ที่ผมจะเพิ่มไปให้มันก็คือการทำงานเพื่อเช็ค สิทธิพื้นฐานของ user นั่นเอง ตรงนี้ผมเอา Zend_Acl เข้ามาช่วย จุดประสงค์ของ MY_Controller มีอย่างเดียวคือ ต้องทำ Bootstrap พื้นฐานในการเช็คตรงนี้ให้ได้ สุดท้ายแล้วผมได้ออกมาหน้าตาแบบนี้ครับ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
 
require_once('Zend/Registry.php');
 
require_once('Zend/Locale.php');
 
class MY_Controller extends Controller {
 
    protected $_controller = "";
 
    protected $_method = "";
 
    public $app_acl = null;
 
    public $app_acl_cache = null;
 
    public function __construct()
    {
        parent::Controller();
 
        // Load user site info
        $user_site_info = user_site_info();
 
        // Set default timezone for application
        $timezone = $this->config->item('timezone');
        date_default_timezone_set($timezone);
 
        // Set default language
        $language = ($user_site_info['locale']) ? $user_site_info['locale'] : $this->config->item('locale');
        $this->lang->setInstance($language);
        $this->lang->load('site_www');
 
 
        $this->_controller = $this->uri->rsegment(1);
        $this->_method = $this->uri->rsegment(2);
 
        // load zend cache for acl
        $this->load->loadClass('Zend_Cache');
 
        // config cache use for access control list
        $cacheFrontends = array(
            'lifetime' =>  86400,
            'automatic_serialization' => true,
            'automatic_cleaning_factor' => 50
        );
        $cacheBackends = array(
            'cache_dir' => config_item('cache_dir') . '/acl',
            'cache_db_complete_path' => config_item('cache_dir') . '/acl/cache.sqlite',
            'file_name_prefix' => 'acl',
            'hashed_directory_umask' => '0777',
            'cache_file_umask' => '644',
            'hashed_directory_level' => '0',
            'server' => config_item('cache_server'),
            'compression' => true
        );
        $this->app_acl_cache = Zend_Cache::factory('Core', config_item('cache_method'), $cacheFrontends, $cacheBackends);
 
        // cache for access control list
        if (!$this->app_acl = $this->app_acl_cache->load('app_acl'))
        {
            $this->app_acl = $this->zacl;
 
            // this may be not necessary if you give permission to the table name `roles_privileges`
            $this->load->model('model_privileges', 'privileges');
            $resources = $this->privileges->getDistinctControllers();
            foreach ($resources as $resource) {
                $this->app_acl->addResource($resource['controller']);
            }
 
            // select all roles
            $this->load->model('model_roles', 'roles');
            $roles = $this->roles->getRoles();
 
            foreach ($roles as $role) {
                $acl[$role['id']]['inherit'] = $role['inherit'];
            }
 
            // select relation betwenn role and privileges
            $this->load->model('model_roles_privileges', 'roles_privileges');
            $roles_privileges = $this->roles_privileges->getRolesPrivileges();
            foreach ($roles_privileges as $roles_privilege)
            {
                // allow and deny data
                if ($roles_privilege['allow'] == '1')
                    $acl[$roles_privilege['role_id']]['allow'][$roles_privilege['privilege_controller']][] = $roles_privilege['privilege_action'];
                else
                    $acl[$roles_privilege['role_id']]['deny'][$roles_privilege['privilege_controller']][] = $roles_privilege['privilege_action'];
            }
 
            if (is_array($acl) && sizeof($acl) > 0):
                foreach ($acl as $role => $data):
                    // inherite from another role
                    if (array_key_exists('inherit', $data) && $data['inherit'] != '')
                        $this->app_acl->addRoleInherit($role, $data['inherit']);
                    else
                        $this->app_acl->addRole($role);
 
                    if (array_key_exists('allow', $data))
                    {
                        foreach ($data['allow'] as $controller => $actions)
                        {
                            foreach ($actions as $action)
                            {
                                if (strcmp($controller, '#all') == 0 && strcmp($action, '#all') == 0)
                                    $this->app_acl->allowPermission($role);
                                else
                                    $this->app_acl->allowPermission($role, $controller, $action);
                            }
                        }
                    }
 
                    if (array_key_exists('deny', $data))
                    {
                        foreach ($data['deny'] as $controller => $actions)
                        {
                            foreach ($actions as $action)
                            {
                                if (strcmp($controller, '#all') == 0 && strcmp($action, '#all') == 0)
                                    $this->app_acl->denyPermission($role);
                                else
                                    $this->app_acl->denyPermission($role, $controller, $action);
                            }
                        }
                    }
 
                endforeach;
            endif; // end if acl
 
            // cache acl
            $this->app_acl_cache->save($this->app_acl, 'app_acl');
 
        } // end acl cache
 
        log_message('debug', 'MY_Controller Class Initialized');
 
        // run access control list
        $role = user_info_data('role_id');
        if (!$this->app_acl->isAllowed($role, $this->_controller, $this->_method))
        {
            redirect('/auth/login?access-denied');
        }
    }
 
}
?>
ตรงนี้ผมก็ไม่รู้จะอธิบายยังไงหมด คือมัน ผูกพันธ์กันไปทั้งเว็บ ผมขอแค่อธิบายเป็น Concept คร่าวๆ ก็แล้วกันนะครับ

ตรง ที่ include Zend_Locale นั้น ไม่ได้หยิบมาใช้ตรงนี้ครับ มันจะถูกไปใช้กับ MY_Language ที่ผมเอา CI มาแก้อีกที ส่วน Zend_Registry ผมก็เอาไปใช้เรื่องอื่น แต่ไหนๆ ตรงนี้มันก็เป็น แกนหลัก ผมเลยเอาฝากไว้เท่านั้นเอง ดังนั้น ตอนนี้ยังไม่ต้องไปสนใจมากก็ได้ครับ

ที่สำคัญก็คือ
1
$user_site_info = user_site_info();
ตรงนี้จะเป็น helper ของผมเองที่ไปเรียก ข้อมูลของ User ที่ทำการ Authen มาอยู่ และจะมีตัวแปลในนั้นนั้นที่ จำเป็นคือ Role_id เป็นตัวบ่งบอกว่า user คนนี้มี สิทธิถึงขั้นไหน

1
2
$this->_controller = $this->uri->rsegment(1);
$this->_method = $this->uri->rsegment(2);
ตรงนี้เป็นส่วนที่ผมดึง Real Segment ของ CI ออกมาเพื่อที่จะทำการ map เข้ากับตาราง สิทธิ อีกทีนึง

ส่วนเรื่อง Cache เป็นตัวช่วยลดการทำงานของ DB เท่านั้นเองครับ

1
$this->app_acl = $this->zacl;
ตรงนี้เป็นตัวถ่าย Class มาจาก Zacl ทื่ผมเอา Zend_Zcl มาขยายอีกทีนึง Class ตัวนี้มีหน้าตาราวๆ นี้ครับ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
 
require_once 'Zend/Acl.php';
 
require_once 'Zend/Acl/Role.php';
 
require_once 'Zend/Acl/Resource.php';
 
class CI_Zacl {
 
    private $_acl = null;
 
    public function __construct()
    {
        $this->_acl = new Zend_Acl();
    }
 
    public function addRole($roleName)
    {
        $this->_acl->addRole(new Zend_Acl_Role($roleName));
    }
 
    public function addRoleInherit($roleName, $inheritFromRole)
    {
        $this->_acl->addRole(new Zend_Acl_Role($roleName), $inheritFromRole);
    }
 
    public function addResource($resource)
    {
        $this->_acl->add(new Zend_Acl_Resource($resource));
    }
 
    public function allowPermission($roleName, $controllerName = null, $actionName = null)
    {
        return $this->permission('allow', $roleName, $controllerName, $actionName);
    }
 
    public function denyPermission($roleName, $controllerName = null, $actionName = null)
    {
        return $this->permission('deny', $roleName, $controllerName, $actionName);
    }
 
    public function permission($type, $roleName, $controllerName, $actionName = null)
    {
        if (!in_array($type, array('deny', 'allow')))
        {
            return false;
        }
 
        if ($this->_acl->hasRole($roleName))
        {
            $this->_acl->{$type}($roleName, $controllerName, $actionName);
            return true;
        }
        return false;
    }
 
    public function isAllowed($roleName, $controllerName, $actionName)
    {
        if ($this->_acl->has($controllerName))
        {
            $roleName = ($this->_acl->hasRole($roleName)) ? $roleName : 'Guest';
            return $this->_acl->isAllowed($roleName, preg_replace('/^controller_/', '', $controllerName), $actionName);
        }
        return true;
    }
 
}
 
?>

กลับมาที่ MY_Controller ของเราต่อนะครับ

หลัง จากที่ผมทำการ Query พวกสิทธิออกมาจาก Database แล้วก็เอามาเข้า สูตรของ Zend_Acl (ถ้าใครยังงเรื่องนี้อยู่ขอให้ไปย้อนดูบทความเก่าๆ ของผมนะครับ)

จุดที่สำคัญที่สุดก็คือ
1
2
3
4
5
6
// run access control list
        $role = user_info_data('role_id');
        if (!$this->app_acl->isAllowed($role, $this->_controller, $this->_method))
        {
            redirect('/auth/login?access-denied');
        }

ตรงนี้ก็คือการเอา Role ของ Authen User มาทำการเช็คเข้ากับ Controller และ Method ที่ใช้งานอยู่ว่า สิทธิพอมั้ย ถ้าพอก็ให้ทำงาน ไม่พอผมสั่งไป Re-Login ใหม่ ก็เป็นอันว่า Bootstrap จาก MY_Controller ของเราเป็นอันเสร็จ

ซึ่งจริงๆ แล้วเรายังสามารถเอา ACL ชุดนี้ไปใช้ใย view ได้อีกด้วย ด้วยการเขียน Helper เล็กๆมา แบบนี้
1
2
3
4
5
6
7
8
9
function is_allowed($controller, $action, $role=null)
{
    $CI =& get_instance();
    if (is_null($role))
    {
        $role = user_info_data('role_id');
    }
    return $CI->app_acl->isAllowed($role, $controller, $action);
}
เท่านี้เราก็จะได้เรื่อง ACL ที่แข็งแรงแลมีประสิทธิภาพแล้วครับ

PS. ต้องขออภัยจริงๆ นะครับ ที่เรื่องนี้ผมสามารถอธิบายได้ดีที่สุด คือแค่ให้ Concept เพราะว่า มันต้องทำเยอะมากๆ จริงๆ กว่าจะออกมาเสร็จสมบูรณ์ ไม่รู้จะอธิบายยังไงให้ครอบคลุม ก็เลยให้ไว้ได้แค่แนวทางครับ เจอกันคราวหน้า ^^
create by http://www.jquerytips.com/blogs/view/1093/Beauty-Your-CI-Step-2-Beauty-My-Controller