jQueryTips by Tee++;

  • Home
  • Videos
  • jQueryTips's Fans

Coding like playing a piano.

Subscribe RSS

Search

Search

Sponsored

jQueryTips on Social

Followers

hide
Chainnnn Pang25441 Dexteri@n Ton KanexKane Supachai JustDoIt Rut petchy Tophit anndrew Webmaster

Categories

hide
  • Programing
  • Database
  • Framework
  • Mobile Development
    • Titanium Mobile
    • PhoneGap
  • Life

Tag Cloud

hide
PHP JavaScript MySQL XML Mobile jQuery CodeIgniter Hash php xml Debug Library Zend API Events Plugins Facebook Basic Registration Tool Twitter Search Swath Performance ffmpeg YouTube Web Service Yahoo! NoSQL MongoDB PhoneGap

Recent Posts

hide
  • ใครๆ ก็มี Utils เอางี้เราเพิ่ม Utils ให้กับ CodeIgniter กันบ้าง
  • แนะนำ JS.Class (A JavaScript class library)
  • PHP กับ OOP มาปูพื้นฐานกันเสียใหม่ก่อน
  • ประกาศข่าวสารจาก Admin
  • JSFIDDLE สุดยอด Tools สำหรับการ Debug การเขียนตัวอย่างโคด
  • Titanium Mobile + HTML + Native Scrolling
  • HTML to PDF ตัวไหนดี วันนี้ผมมีคำตอบ ...
  • ทำความรู้จักกับ JavaScript Pattern ในเชิงลึก
  • ผมบอกคุณแล้ว .... คนโง่ก็ Code ได้
  • วันนี้ มาออกแบบ Web Service ด้วย PHP กันเถอะ (CodeIgniter Version)

Recent Comments

hide
  • เยดดดด
  • มีตัวอย่าง CI แบบ เพียว ๆ ไหมครับ
  • ขอบคุณมากครับ เห็นแนวทางได้ชัดขึ้นจากตอนแรก ที่งงไปหมด ไม่รู้อะไรเป็นอะไร
  • ขอบคุณมากเลยครับท่าน....แหม่เพิ่มกำลังใจให้ผมได้เยอะเลย ตอนนี้กำลังฝึกงานอยู่ที่แห่งนึงในหาดใหญ่ พร้อมเด็กจากอีกสถาบันนึง 3 คน เราคิดว่าเรา…
  • Very good blog, I really like ~

Links

hide
  • OSCOOL
  • Architeture & Performance
  • Video and TV
  • Appcelerator
  • PhoneGap
  • Home
  •  » Blogs

Beauty Your CI Step 2 - Beauty My Controller

Dec 14, 2010 11:22:47 PM | 9 Comments | in Framework | CodeIgniter
เอาล่ะครับ มาต่อกันจากปีที่แล้ว 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 พื้นฐานในการเช็คตรงนี้ให้ได้ สุดท้ายแล้วผมได้ออกมาหน้าตาแบบนี้ครับ

<?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 เพราะว่า มันต้องทำเยอะมากๆ จริงๆ กว่าจะออกมาเสร็จสมบูรณ์ ไม่รู้จะอธิบายยังไงให้ครอบคลุม ก็เลยให้ไว้ได้แค่แนวทางครับ เจอกันคราวหน้า ^^



twitter stumbleupon delicious digg facebook

9 comments

Add comment Load previous All comments

Leave a comment

Post Comment

Powered by OSCOOL. You may view this on RSS or ATOM.

OSCOOL

  • Twitter
  • Facebook
  • Next