MVC CMS - Mô hình hoạt động của TzCMS

PHP MVC 17/05/2005 07:00 720
Tiếp tục các bài viết trong series Hướng dẫn xây dựng CMS theo chuẩn MVC. Trong bài viết này mình sẽ mô tả chi tiết về mô hình hoạt động của TzCMS. Xây dựng hàm __autoload() hay cơ chế load controller trong TzCMS.

Như đã đề cập trước đó, tệp tin .htaccess sẽ chuyển hướng tất cả yêu cầu tới tệp index.php trong thư mục gốc. Trước hết, tệp config.php sẽ xác định một hằng số ROOT. Hằng số này sẽ đóng vai trò là một biến bảo mật và nó sẽ được kiểm tra bởi từng tệp sao cho không ai có thể truy cập trực tiếp vào bất kỳ tệp nào khác. Nói cách khác, tất cả các tệp khác (trừ tệp tĩnh) chỉ có thể được mở bởi tệp index.php.

Sau đó các cấu hình từ tệp config.php và init.php được tìm nạp và lưu trữ các hằng để các tệp khác có thể sử dụng chúng. Sau đó chúng ta định nghĩa một biến session có tên csrf_tocken. Biến này sẽ được xác minh khi chúng ta gửi bất kỳ form nào. Nó đảm bảo rằng yêu cầu gửi dữ liệu từ các FORM biểu từ một trang web sẽ được xử lý bởi hệ thống.

1. Tệp tin .htaccess

Tập tin .htaccess được sử dụng bởi máy chủ. Nó cung cấp một cách để bạn có thể thực hiện các thay đổi cấu hình máy chủ trực tiếp trên cơ sở mỗi thư mục. Các yêu cầu được chuyển đến hệ thống sẽ được lọc bởi tệp tin này.

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond $1 !^(images|photos|css|js|robots\.txt)
RewriteRule ^(.+)$ index.php/$1 [NC,L]
  • Backend: Trong phân vùng này có lẽ sẽ không đả động gì tơi htaccess.
  • Frontend: Tôi sẽ sử dụng htaccess để làm cho URL chuẩn SEO đẹp và thân thiện với các Searc Engine

Ví dụ:

Mình có một đường dẫn như sau để xem chi tiết một bản tin

http://example.com/ban-tin/mvc-cms-xay-dung-thu-vien-xu-ly-lop-session.html

Mình sẽ phân tích

  • Controller: news
  • Action: detail
  • Slug: mvc-cms-xay-dung-thu-vien-xu-ly-lop-session.

Mình sẽ có .htaccess

RewriteRule ^ban-tin/(.*).html index.php?c=news&a=detail&slug=$1 [L]

2. Tạo file index.php

Chúng ta sẽ xử lý tất cả yêu cầu bằng cách sử dụng index.php.

define('_SITE_ROOT', false);

Ở đây mình khai báo một hằng số là _SITE_ROOT cho phép chúng ta xác định đây là file index.php của Frontend.

Tương tự như vậy trong file index.php của thư mục admin. Mình cũng định nghĩa một hằng số _SITE_ROOT 

define('_SITE_ROOT', 'admin');

Cho phép hoặc không hiển thị những thông báo lỗi trong ứng dụng.

define('DEVELOPMENT_ENVIRONMENT', TRUE);
  • Include file config.php
  • Include file init.php

Include file /bootstrap.php. File này đóng vai trò là điều khiển controlleraction bằng cách nhận trực tiếp các tham số từ URI.

<?php 
	define('_SITE_ROOT', false);
	require_once "config.php";
	require_once(ROOT . '/core/init.php');
	
	if(DEVELOPMENT_ENVIRONMENT===TRUE){
		error_reporting(E_ALL);
		ini_set('display_errors','On');
	}else{
		error_reporting(E_ALL);
		ini_set('display_errors','Off');
		ini_set('log_errors', 'On');
		ini_set('error_log', ROOT.'logs'.DS.'error.log');
	}
	/*
	|------------------------------------------
	| Required Module
	|------------------------------------------
	*/
	if (file_exists(DIR_CORE."/bootstrap.php")){
		require_once(DIR_CORE."/bootstrap.php");
	}else{
		trigger_error('ISOCMS not found!',E_USER_ERROR);
	}
?>

3. Tạo file config.php 

Mình sẽ nói về file config.php trong bài viết xây dựng thư viện xử lý config.

4. Tạo file init.php

Trong tệp file này sẽ định nghĩa những hằng số hệ thống.

/*
|---------------------------------------
| Definition constants global
|---------------------------------------
*/
define('SITE_URL', (is_ssl()?'https://':'http://').$_SERVER['HTTP_HOST'].dirname($_SERVER['SCRIPT_NAME']));
/*
|---------------------------------------
| Common Directory
|---------------------------------------
*/
define("DIR_ADODB", DIR_CORE."/adodb");
define('DIR_COMMON', DIR_CORE.'/common');
define("DIR_SMARTY", DIR_CORE."/smarty");
define("DIR_LIB", DIR_CORE."/libraries");
define('DIR_APPLICATION', ROOT.(_SITE_ROOT=='admin'?'/'._SITE_ROOT:'').'/application');
define('DIR_CONTROLLER', DIR_APPLICATION . '/controller');
define('DIR_BLOCKS', DIR_APPLICATION . '/blocks');
/*
|---------------------------------------
| Common directory themes
|---------------------------------------
*/
define('DIR_TEMPLATES', DIR_APPLICATION . '/templates');
define('DIR_TEMPLATES_C', DIR_APPLICATION . '/templates_c');
define("URL_TEMPLATES", SITE_URL."/application/templates");
define("URL_THEMES", URL_TEMPLATES."/skin");
define("URL_CSS", URL_TEMPLATES."/css");
define("URL_JS", URL_TEMPLATES."/js");
define("URL_IMAGES", URL_TEMPLATES."/images");

Lưu ý:

define('DIR_APPLICATION', ROOT.(_SITE_ROOT=='admin'?'/'._SITE_ROOT:'').'/application');

Khi đó đường dẫn tới thư mục application của Frontend là /application và Backend /admin/application.

Tiếp theo là hàm __autoload() hàm này cho phép tự động include các class file trong thư mục core/commen và và load các core/models.

function __autoload($class_name){
	// Note : All classes file have a lowercase name. the class name must be lowercase
	$common_path = DIR_CORE.'/common/'.strtolower($class_name).'.class.php';
	$model_path = DIR_CORE.'/models/'.strtolower($class_name).'.class.php';
	// Check file existence before including the if 
	if ( file_exists($common_path) ) {
		require_once($common_path);
	} else if( file_exists($model_path) ){
		require_once ($model_path);
	}else{
		throw new Exception('Failed to include class');
	}
}

Full code file init.php

<?php 
	session_start();
	ob_start();
	/*
	|-----------------------------------------------------
	| General file
	|-----------------------------------------------------
	*/
	require_once "general.php";
	/*
	|---------------------------------------
	| Definition constants global
	|---------------------------------------
	*/
	define('SITE_URL', (is_ssl()?'https://':'http://').$_SERVER['HTTP_HOST'].dirname($_SERVER['SCRIPT_NAME']));
	/*
	|---------------------------------------
	| Common Directory
	|---------------------------------------
	*/
	define("DIR_ADODB", DIR_CORE."/adodb");
	define('DIR_COMMON', DIR_CORE.'/common');
	define("DIR_SMARTY", DIR_CORE."/smarty");
	define("DIR_LIB", DIR_CORE."/libraries");
	define('DIR_APPLICATION', ROOT.(_SITE_ROOT=='admin'?'/'._SITE_ROOT:'').'/application');
	define('DIR_CONTROLLER', DIR_APPLICATION . '/controller');
	define('DIR_BLOCKS', DIR_APPLICATION . '/blocks');
	/*
	|---------------------------------------
	| Common directory themes
	|---------------------------------------
	*/
	define('DIR_TEMPLATES', DIR_APPLICATION . '/templates');
	define('DIR_TEMPLATES_C', DIR_APPLICATION . '/templates_c');
	define("URL_TEMPLATES", SITE_URL."/application/templates");
	define("URL_THEMES", URL_TEMPLATES."/skin");
	define("URL_CSS", URL_TEMPLATES."/css");
	define("URL_JS", URL_TEMPLATES."/js");
	define("URL_IMAGES", URL_TEMPLATES."/images");
	/*
	|-----------------------------------------------------
	| Set default Exception handler if not in debug mode
	|-----------------------------------------------------
	*/
	if(!DEBUG_MODE){
		function exception_handler($exception) {
			echo $exception->getMessage();
		}
		set_exception_handler('exception_handler');
	}
	/*
	|-----------------------------------------------------
	| Include file requirement
	|-----------------------------------------------------
	*/
	function __autoload($class_name){
		// Note : All classes file have a lowercase name. the class name must be lowercase
		$common_path = DIR_CORE.'/common/'.strtolower($class_name).'.class.php';
		$model_path = DIR_CORE.'/models/'.strtolower($class_name).'.class.php';
		// Check file existence before including the if 
		if ( file_exists($common_path) ) {
			require_once($common_path);
		} else if( file_exists($model_path) ){
			require_once ($model_path);
		}else{
			throw new Exception('Failed to include class');
		}
	}
	/*
	|-----------------------------------------------------
	| Session 
	|-----------------------------------------------------
	*/
	if(!Session::setup()){
		trigger_error('Session setup failed', E_USER_ERROR);
		exit();
	}
	/*
	|-----------------------------------------------------
	| Add CSRF token 
	|-----------------------------------------------------
	*/
	if(Config::get('csrf/csrf_protection')==TRUE){
		$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$charactersLength = strlen($characters);
		$randomString = '';
		for ($i = 0; $i < 32; $i++) {
			// choosing one character from all characters and adding it to random string
			$randomString .= $characters[rand(0, $charactersLength - 1)];
		}
		// store generated csrf token using sha512 hashing
		Session::put('csrf_token',Hash::make(time().Config::get('system/encryption_key').$randomString));
		
		if($_SERVER['REQUEST_METHOD']!=='GET'){
			if(!isset($_REQUEST[Config::get('csrf/csrf_token_name')]) 
			|| $_REQUEST[Config::get('csrf/csrf_token_name')] !== Session::get('csrf_token')){
				// if wrong csrf_token stop user from accessing
				header('HTTP/1.1 403 Forbidden');
				exit;
			}
		}
	}
?>

5. File bootstrap.php

Trong file này chúng ta chỉ tập xử lý request người dùng để hệ thông tự động Include file controller nào và run action nào.

Đồng thời trong file này mình sẽ Include vào các file __header.php và __footer.php.

Mình sẽ có một bài viết về xây dựng lớp xử lý Controller.

Đầu tiên thì mình sẽ lấy các tham số về controller và action trên URI.

Ví dụ: http://localhost/TzCMS/index.php?c=news&a=cat.

$controller = strtolower(Input::get('c', DEFAULT_CONTROLLER));
$action = strtolower(Input::get('a', DEFAULT_ACTION));

Lưu ý: Trong trường hợp trên URI không chứa các tham số controlleraction thì hệ thống sẽ trả về controlleraction mặc định.

define('DEFAULT_CONTROLLER', 'home');
define('DEFAULT_ACTION', 'index');

Khai báo một đối tượng lớp core.

/*
|---------------------------------------
| Core constructor
|---------------------------------------
*/
$core = Core::getInstance();

Full code file boostrap.php

<?php if (!defined('ROOT')) exit('No direct script access allowed'); 
	/*
	|------------------------------------------------------------
	| TzCMS - Content management system website
	| @Author	: Tz.Thiêm (buivanthiem.it@gmail.com)
	| @Date		: 2009/1/18
	| @Version	: 2.1.1
	| @Copyright (c) TzCMS. All Rights Reserved,
	|------------------------------------------------------------
	| Email: info@chiasephp.net
	| Website: http://www.chiasephp.net
	| -----------------------------------------------------------
	*/
	
	$controller = strtolower(Input::get('c', 'home'));
	$action = strtolower(Input::get('a', 'default'));
	/*
	|---------------------------------------
	| Core constructor
	|---------------------------------------
	*/
	$core = Core::getInstance();
	
	require(DIR_APPLICATION.'/_header.php');
	/*
	|---------------------------------------
	| Run controller
	|---------------------------------------
	*/
	$controllerInstance = Controller::getInstance(
		array(
			'controller' => $controller,
			'action'	=> $action
		)
	);
	$controllerInstance->run();
	require(DIR_APPLICATION.'/_footer.php');
	
?>

Trong file boostrap mình có sử dụng một thư viện xử lý Input. Thư viện này cho phép hệ thống nhận các tham số từ URI hoặc dữ liệu từ những FORM gửi nên.

File /core/common/input.class.php

<?php if (!defined('ROOT')) exit('No direct script access allowed'); 
	/*
	|------------------------------------------------------------
	| TzCMS - Content management system website
	| @Author	: Tz.Thiêm (buivanthiem.it@gmail.com)
	| @Date		: 2009/1/18
	| @Version	: 2.1.1
	| @Copyright (c) TzCMS. All Rights Reserved,
	|------------------------------------------------------------
	| Email: info@chiasephp.net
	| Website: http://www.chiasephp.net
	| -----------------------------------------------------------
	*/
	class Input{
		var $GET = array();
		var $POST = array();
		var $allow_unicode = true;
	
		function parse_incoming($flag = true){
			global $HTTP_GET_VARS, $HTTP_POST_VARS;
			$HTTP_GET_VARS = !empty($HTTP_GET_VARS) 
				? $HTTP_GET_VARS : $_GET;
			$HTTP_POST_VARS = !empty($HTTP_POST_VARS) 
				? $HTTP_POST_VARS : $_POST;
			$return = array();
			if ($flag == true && is_array($HTTP_GET_VARS)) {
				while (list($k, $v) = each($HTTP_GET_VARS)) {
					if (is_array($HTTP_GET_VARS[$k])) {
						while (list($k2, $v2) = each($HTTP_GET_VARS[$k])) {
							$return[$k][$this->clean_key($k2) ] = $this->clean_value($v2);
						}
					}
					else {
						$return[$k] = $this->clean_value($v);
					}
				}
			}
			// Overwrite GET data with post data
			if ($flag != true && is_array($HTTP_POST_VARS)) {
				while (list($k, $v) = each($HTTP_POST_VARS)) {
					if (is_array($HTTP_POST_VARS[$k])) {
						while (list($k2, $v2) = each($HTTP_POST_VARS[$k])) {
							$return[$k][$this->clean_key($k2) ] = $this->clean_value($v2);
						}
					}
					else {
						$return[$k] = $this->clean_value($v);
					}
				}
			}
			return $return;
		}
		/*
		|------------------------------------
		| Clean key
		|------------------------------------
		*/
		function clean_key($key){
			if ($key == "0") return $key;
			if ($key == "") {
				return "";
			}
			$key = preg_replace("/\.\./", "", $key);
			$key = preg_replace("/\_\_(.+?)\_\_/", "", $key);
			$key = preg_replace("/^([\w\.\-\_]+)$/", "$1", $key);
			return $key;
		}
		/*
		|------------------------------------
		| Clean key
		|------------------------------------
		*/
		function clean_value($val){
			if ($val == "") { return ""; }
			
			$val = str_replace("&#032;", " ", $val);
			$val = str_replace(chr(0xCA) , "", $val); //Remove sneaky spaces
			$val = str_replace("&", "&amp;", $val);
			$val = str_replace("<!--", "&#60;&#33;--", $val);
			$val = str_replace("-->", "--&#62;", $val);
			$val = preg_replace("/<script/i", "&#60;script", $val);
			$val = str_replace(">", "&gt;", $val);
			$val = str_replace("<", "&lt;", $val);
			$val = str_replace("\"", "&quot;", $val);
			$val = preg_replace("/\n/", "<br />", $val); // Convert literal newlines
			$val = preg_replace("/\\\$/", "&#036;", $val);
			$val = preg_replace("/\r/", "", $val); // Remove literal carriage returns
			$val = str_replace("!", "&#33;", $val);
			$val = str_replace("'", "&#39;", $val); // IMPORTANT: It helps to increase sql query safety.
			
			// Ensure unicode chars are OK
			if ($this->allow_unicode) {
				$val = preg_replace("/&amp;#([0-9]+);/s", "&#\\1;", $val);
			}
			
			// Strip slashes if not already done so.
			if (get_magic_quotes_gpc()) {
				$val = stripslashes($val);
			}
			
			// Swop user inputted backslashes
			$val = preg_replace("/\\\(?!&amp;#|\?#)/", "&#092;", $val);
			return $val;
		}
		/*
		|-------------------------------------------
		| METHOD GET
		|-------------------------------------------
		*/
		public static function get($var, $def = ""){
			return isset($_GET[$var]) ? $_GET[$var] : $def;
		}
		/*
		|-------------------------------------------
		| METHOD POST
		|-------------------------------------------
		*/
		public static function post($var, $def = ""){
			return isset($_POST[$var]) ? $_POST[$var] : $def;
		}
	}
?>

Tổng kết.

Xem Thêm

Profile photo of adminTheHalfHeart

B.V.T

Sinh ra và lớn nên ở Bắc Giang. Hiện tại thì tôi đang là một lập trình viên tại VietISO. Tôi lập website này với mục đích là bookmark những gì tôi đã đọc qua và mong muốn chia sẻ những gì tôi biết.