Beauty Your CI Step 2 - Beauty My Controller
เอาล่ะครับ มาต่อกันจากปีที่แล้ว 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 พื้นฐานในการเช็คตรงนี้ให้ได้ สุดท้ายแล้วผมได้ออกมาหน้าตาแบบนี้ครับ
ตรงที่ include Zend_Locale นั้น ไม่ได้หยิบมาใช้ตรงนี้ครับ มันจะถูกไปใช้กับ MY_Language ที่ผมเอา CI มาแก้อีกที ส่วน Zend_Registry ผมก็เอาไปใช้เรื่องอื่น แต่ไหนๆ ตรงนี้มันก็เป็น แกนหลัก ผมเลยเอาฝากไว้เท่านั้นเอง ดังนั้น ตอนนี้ยังไม่ต้องไปสนใจมากก็ได้ครับ
ที่สำคัญก็คือ
ส่วนเรื่อง Cache เป็นตัวช่วยลดการทำงานของ DB เท่านั้นเองครับ
กลับมาที่ MY_Controller ของเราต่อนะครับ
หลังจากที่ผมทำการ Query พวกสิทธิออกมาจาก Database แล้วก็เอามาเข้า สูตรของ Zend_Acl (ถ้าใครยังงเรื่องนี้อยู่ขอให้ไปย้อนดูบทความเก่าๆ ของผมนะครับ)
จุดที่สำคัญที่สุดก็คือ
ตรงนี้ก็คือการเอา Role ของ Authen User มาทำการเช็คเข้ากับ Controller และ Method ที่ใช้งานอยู่ว่า สิทธิพอมั้ย ถ้าพอก็ให้ทำงาน ไม่พอผมสั่งไป Re-Login ใหม่ ก็เป็นอันว่า Bootstrap จาก MY_Controller ของเราเป็นอันเสร็จ
ซึ่งจริงๆ แล้วเรายังสามารถเอา ACL ชุดนี้ไปใช้ใย view ได้อีกด้วย ด้วยการเขียน Helper เล็กๆมา แบบนี้
PS. ต้องขออภัยจริงๆ นะครับ ที่เรื่องนี้ผมสามารถอธิบายได้ดีที่สุด คือแค่ให้ Concept เพราะว่า มันต้องทำเยอะมากๆ จริงๆ กว่าจะออกมาเสร็จสมบูรณ์ ไม่รู้จะอธิบายยังไงให้ครอบคลุม ก็เลยให้ไว้ได้แค่แนวทางครับ เจอกันคราวหน้า ^^
ช่วงนี้ผมไปวุ่นๆ กับไอ้ 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 พื้นฐานในการเช็คตรงนี้ให้ได้ สุดท้ายแล้วผมได้ออกมาหน้าตาแบบนี้ครับ
<?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 ผมก็เอาไปใช้เรื่องอื่น แต่ไหนๆ ตรงนี้มันก็เป็น แกนหลัก ผมเลยเอาฝากไว้เท่านั้นเอง ดังนั้น ตอนนี้ยังไม่ต้องไปสนใจมากก็ได้ครับ
ที่สำคัญก็คือ
$user_site_info = user_site_info();ตรงนี้จะเป็น helper ของผมเองที่ไปเรียก ข้อมูลของ User ที่ทำการ Authen มาอยู่ และจะมีตัวแปลในนั้นนั้นที่ จำเป็นคือ Role_id เป็นตัวบ่งบอกว่า user คนนี้มี สิทธิถึงขั้นไหน
$this->_controller = $this->uri->rsegment(1); $this->_method = $this->uri->rsegment(2);ตรงนี้เป็นส่วนที่ผมดึง Real Segment ของ CI ออกมาเพื่อที่จะทำการ map เข้ากับตาราง สิทธิ อีกทีนึง
ส่วนเรื่อง Cache เป็นตัวช่วยลดการทำงานของ DB เท่านั้นเองครับ
$this->app_acl = $this->zacl;ตรงนี้เป็นตัวถ่าย Class มาจาก Zacl ทื่ผมเอา Zend_Zcl มาขยายอีกทีนึง Class ตัวนี้มีหน้าตาราวๆ นี้ครับ
<?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 (ถ้าใครยังงเรื่องนี้อยู่ขอให้ไปย้อนดูบทความเก่าๆ ของผมนะครับ)
จุดที่สำคัญที่สุดก็คือ
// 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 เล็กๆมา แบบนี้
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 เพราะว่า มันต้องทำเยอะมากๆ จริงๆ กว่าจะออกมาเสร็จสมบูรณ์ ไม่รู้จะอธิบายยังไงให้ครอบคลุม ก็เลยให้ไว้ได้แค่แนวทางครับ เจอกันคราวหน้า ^^

9 comments