首先,我們在專案中建立如下目錄和檔案:PHP的框架眾多,對於哪個框架最好,哪個框架最爛,是否應該用框架,對於這些爭論在論壇裡面都有人爭論,這裡不做評價,個人覺得根據自己需求,選中最佳最適合自己MVC框架,並在開發中能夠體現出敏捷開發的效果就OK了,作為一個PHPer要提高自己的對PHP和MVC的框架的認識,所以自己寫一個MVC框架是很有必要的,即使不是很完善,但是自己動手寫一個輕量簡潔的PHP MVC框架起碼對MVC的思想有一定的了解,而且經過自己後期的完善會漸漸形成一個自己熟悉的一個PHP框架。來寫一個PHP MVC框架開發的簡明教程,首先宣告,教程裡面的框架不是一個完善的框架,只是一種思路,當然每個人對MVC框架實現的方法肯定是有差異的,希望高手多提意見多指正,和我一樣的菜鳥多討論多交流,剛接觸MVC的PHPer多學習。
app|-controller 存放控制器檔案|-model 存放模型檔案|-view 存放檢視檔案|-lib 存放自定義類庫|-config 存放配置檔案|--config.php 系統配置檔案|-system 系統核心目錄|-index.php 入口檔案
新件的index.php為入口檔案,我們這裡採用單一入口,入口檔案的內容很簡單:
<?php /** * 應用入口檔案 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ require dirname(__FILE__).'/system/app.php'; require dirname(__FILE__).'/config/config.php'; Application::run($CONFIG); 入口檔案主要做了2件事,第一引入系統的驅動類,第二是引入配置檔案,然後執行run()方法,傳入配置作為引數,具體這2個檔案是什麼內容,我們接下來繼續看。 先看一下config/config.php檔案,裡面其實是一個$CONFIG變數,這個變數存放的全域性的配置: <?php /** * 系統配置檔案 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ /*資料庫配置*/ $CONFIG['system']['db'] = array( 'db_host' => 'localhost', 'db_user' => 'root', 'db_password' => '', 'db_database' => 'app', 'db_table_prefix' => 'app_', 'db_charset' => 'urf8', 'db_conn' => '', //資料庫連線標識; pconn 為長久連結,預設為即時連結 ); /*自定義類庫配置*/ $CONFIG['system']['lib'] = array( 'prefix' => 'my' //自定義類庫的檔案字首 ); $CONFIG['system']['route'] = array( 'default_controller' => 'home', //系統預設控制器 'default_action' => 'index', //系統預設控制器 'url_type' => 1 /*定義URL的形式 1 為普通模式 index.php?c=controller&a=action&id=2 * 2 為PATHINFO index.php/controller/action/id/2(暫時不實現) */ ); /*快取配置*/ $CONFIG['system']['cache'] = array( 'cache_dir' => 'cache', //快取路徑,相對於根目錄 'cache_prefix' => 'cache_',//快取檔名字首 'cache_time' => 1800, //快取時間預設1800秒 'cache_mode' => 2, //mode 1 為serialize ,model 2為儲存為可執行檔案 );
我這裡有意識的定義$CONFIG['system']陣列表示是系統的配置檔案,當然你可以在裡面定義$CONFIG['myconfig']為表示在定義的配置,以後在程式的控制器,模型,檢視中來呼叫,都個很自由。具體配置值代表什麼意思注視很清楚了,下面的如果程式中有詳細註釋的我就不解釋啦,呵呵
<?php /** * 應用驅動類 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ define('SYSTEM_PATH', dirname(__FILE__)); define('ROOT_PATH', substr(SYSTEM_PATH, 0,-7)); define('SYS_LIB_PATH', SYSTEM_PATH.'/lib'); define('APP_LIB_PATH', ROOT_PATH.'/lib'); define('SYS_CORE_PATH', SYSTEM_PATH.'/core'); define('CONTROLLER_PATH', ROOT_PATH.'/controller'); define('MODEL_PATH', ROOT_PATH.'/model'); define('VIEW_PATH', ROOT_PATH.'/view'); define('LOG_PATH', ROOT_PATH.'/error/'); final class Application { public static $_lib = null; public static $_config = null; public static function init() { self::setAutoLibs(); require SYS_CORE_PATH.'/model.php'; require SYS_CORE_PATH.'/controller.php'; } /** * 建立應用 * @access public * @param array $config */ public static function run($config){ self::$_config = $config['system']; self::init(); self::autoload(); self::$_lib['route']->setUrlType(self::$_config['route']['url_type']); $url_array = self::$_lib['route']->getUrlArray(); self::routeToCm($url_array); } /** * 自動載入類庫 * @access public * @param array $_lib */ public static function autoload(){ foreach (self::$_lib as $key => $value){ require (self::$_lib[$key]); $lib = ucfirst($key); self::$_lib[$key] = new $lib; } //初始化cache if(is_object(self::$_lib['cache'])){ self::$_lib['cache']->init( ROOT_PATH.'/'.self::$_config['cache']['cache_dir'], self::$_config['cache']['cache_prefix'], self::$_config['cache']['cache_time'], self::$_config['cache']['cache_mode'] ); } } /** * 載入類庫 * @access public * @param string $class_name 類庫名稱 * @return object */ public static function newLib($class_name){ $app_lib = $sys_lib = ''; $app_lib = APP_LIB_PATH.'/'.self::$_config['lib']['prefix'].'_'.$class_name.'.php'; $sys_lib = SYS_LIB_PATH.'/lib_'.$class_name.'.php'; if(file_exists($app_lib)){ require ($app_lib); $class_name = ucfirst(self::$_config['lib']['prefix']).ucfirst($class_name); return new $class_name; }else if(file_exists($sys_lib)){ require ($sys_lib); return self::$_lib['$class_name'] = new $class_name; }else{ trigger_error('載入 '.$class_name.' 類庫不存在'); } } /** * 自動載入的類庫 * @access public */ public static function setAutoLibs(){ self::$_lib = array( 'route' => SYS_LIB_PATH.'/lib_route.php', 'mysql' => SYS_LIB_PATH.'/lib_mysql.php', 'template' => SYS_LIB_PATH.'/lib_template.php', 'cache' => SYS_LIB_PATH.'/lib_cache.php', 'thumbnail' => SYS_LIB_PATH.'/lib_thumbnail.php' ); } /** * 根據URL分發到Controller和Model * @access public * @param array $url_array */ public static function routeToCm($url_array = array()){ $app = ''; $controller = ''; $action = ''; $model = ''; $params = ''; if(isset($url_array['app'])){ $app = $url_array['app']; } if(isset($url_array['controller'])){ $controller = $model = $url_array['controller']; if($app){ $controller_file = CONTROLLER_PATH.'/'.$app.'/'.$controller.'Controller.php'; $model_file = MODEL_PATH.'/'.$app.'/'.$model.'Model.php'; }else{ $controller_file = CONTROLLER_PATH.'/'.$controller.'Controller.php'; $model_file = MODEL_PATH.'/'.$model.'Model.php'; } }else{ $controller = $model = self::$_config['route']['default_controller']; if($app){ $controller_file = CONTROLLER_PATH.'/'.$app.'/'.self::$_config['route']['default_controller'].'Controller.php'; $model_file = MODEL_PATH.'/'.$app.'/'.self::$_config['route']['default_controller'].'Model.php'; }else{ $controller_file = CONTROLLER_PATH.'/'.self::$_config['route']['default_controller'].'Controller.php'; $model_file = MODEL_PATH.'/'.self::$_config['route']['default_controller'].'Model.php'; } } if(isset($url_array['action'])){ $action = $url_array['action']; }else{ $action = self::$_config['route']['default_action']; } if(isset($url_array['params'])){ $params = $url_array['params']; } if(file_exists($controller_file)){ if (file_exists($model_file)) { require $model_file; } require $controller_file; $controller = $controller.'Controller'; $controller = new $controller; if($action){ if(method_exists($controller, $action)){ isset($params) ? $controller ->$action($params) : $controller ->$action(); }else{ die('控制器方法不存在'); } }else{ die('控制器方法不存在'); } }else{ die('控制器不存在'); } } }
我叫它框架驅動類,也許不合適,但是我是這樣理解的,它用來啟動這個框架,做好一些初始化的工作,下面我來詳細分析一下每個方法的功能:1.首先時定義了一些常量,很明了,不解釋了2.setAutoLibs 這個方法其實就是設定那些是系統啟動時自動載入的類庫,類庫檔案都存放在SYS_LIB_PATH下面,以lib_開頭的,當然這裡你可以根據自己的規則來命名3.autoload 這個方法就是用來引入你要自動載入的類,然後來例項化,用$_lib陣列來儲存類的例項,比如$lib['route']是system/lib/lib_route.php中lib_route類的例項4.newLib 這個方法是用來載入你自定義的類的,自定義類存放在根目錄下的lib中,但是自定義的類的檔案字首是你自己定義的,看系統配置檔案裡面有,我定義的是my,這樣我就可以在lib目錄下新建一個自定義的類了,比如 my_test.php
<?php class MyTest { function __construct() { echo "my lib test"; } }
為什麼類名這樣命名,看下newLib方法的實現就知道,其實這些你完全可以定義自己的規則,這個方法會首先去著lib下面有沒有這個類,如果有就會引入例項化,如果沒有就去找系統目錄下面的類,有就例項化5.init 就是一個初始化的方法,裡面其實就是載入自動載入的類,以及引入核心控制器和核心模型,這個2個核心檔案過會我們再來分析6.run 方法就是啟動這個框架的了,裡面的最後2步很重要,就是獲取URL然後拆分成一個數組的形似,然後由routeToCm來分發到Controller和Model7.routeToCm 很重要,根據URL分發到Controller和Model,這個我們過會來說
在run方法中
self::$_lib['route']->setUrlType(self::$_config['route']['url_type']); //設定url的
型別
$url_array = self::$_lib['route']->getUrlArray(); //將url轉發成陣列
好吧,我們來看下route的系統類到底做了說明
<?php /** * URL處理類 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ final class Route{ public $url_query; public $url_type; public $route_url = array(); public function __construct() { $this->url_query = parse_url($_SERVER['REQUEST_URI']); } /** * 設定URL型別 * @access public */ public function setUrlType($url_type = 2){ if($url_type > 0 && $url_type <3){ $this->url_type = $url_type; }else{ trigger_error("指定的URL模式不存在!"); } } /** * 獲取陣列形式的URL * @access public */ public function getUrlArray(){ $this->makeUrl(); return $this->route_url; } /** * @access public */ public function makeUrl(){ switch ($this->url_type){ case 1: $this->querytToArray(); break; case 2: $this->pathinfoToArray(); break; } } /** * 將query形式的URL轉化成陣列 * @access public */ public function querytToArray(){ $arr = !empty ($this->url_query['query']) ?explode('&', $this->url_query['query']) :array(); $array = $tmp = array(); if (count($arr) > 0) { foreach ($arr as $item) { $tmp = explode('=', $item); $array[$tmp[0]] = $tmp[1]; } if (isset($array['app'])) { $this->route_url['app'] = $array['app']; unset($array['app']); } if (isset($array['controller'])) { $this->route_url['controller'] = $array['controller']; unset($array['controller']); } if (isset($array['action'])) { $this->route_url['action'] = $array['action']; unset($array['action']); } if(count($array) > 0){ $this->route_url['params'] = $array; } }else{ $this->route_url = array(); } } /** * 將PATH_INFO的URL形式轉化為陣列 * @access public */ public function pathinfoToArray(){ } } 注意querytToArray方法,將將query形式的URL轉化成陣列,比如原來是localhost/myapp/index.php/app=admin&controller=index&action=edit&id=9&fid=10 這樣的url就會被轉發成如下的陣列 array( 'app' =>'admin', 'controller' =>'index', 'action' =>'edit', 'id' =>array( 'id' =>9, 'fid' =>10 ) )
這下再耐心來看下我寫的笨拙的routeToCm,來通過陣列引數來分發到控制器,找到控制器以後還要引用相應的模型,然後就例項化控制器和模型,呵呵,貌似有點成型了。
下面就要開始實現 控制器-模型-檢視了 我們的思路是這樣的,建立一個核心模型和核心控制器,在以後自己的模型和控制器中來繼承核心模型和控制器,核心模型和控制器中主要可以是一些通用的方法和必須的組建的載入,下面我們先來寫核心控制器, 新建system/core/controller.php
<?php /** * 核心控制器 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ class Controller{ public function __construct() { // header('Content-type:text/html;chartset=utf-8'); } /** * 例項化模型 * @access final protected * @param string $model 模型名稱 */ final protected function model($model) { if (empty($model)) { trigger_error('不能例項化空模型'); } $model_name = $model . 'Model'; return new $model_name; } /** * 載入類庫 * @param string $lib 類庫名稱 * @param Bool $my 如果FALSE預設載入系統自動載入的類庫,如果為TRUE則載入非自動載入類庫 * @return object */ final protected function load($lib,$auto = TRUE){ if(empty($lib)){ trigger_error('載入類庫名不能為空'); }elseif($auto === TRUE){ return Application::$_lib[$lib]; }elseif($auto === FALSE){ return Application::newLib($lib); } } /** * 載入系統配置,預設為系統配置 $CONFIG['system'][$config] * @access final protected * @param string $config 配置名 */ final protected function config($config){ return Application::$_config[$config]; } /** * 載入模板檔案 * @access final protect * @param string $path 模板路徑 * @return string 模板字串 */ final protected function showTemplate($path,$data = array()){ $template = $this->load('template'); $template->init($path,$data); $template->outPut(); } }
註釋都寫的很清楚了吧,其實很簡單,這裡的載入模板的方法中load了一個系統自動載入的模板類,這個類我們在建立檢視的時候再來講,然後我們再來建核心模型的檔案system/core/model.php
<?php /** * 核心模型類 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ class Model { protected $db = null; final public function __construct() { header('Content-type:text/html;chartset=utf-8'); $this->db = $this->load('mysql'); $config_db = $this->config('db'); $this->db->init( $config_db['db_host'], $config_db['db_user'], $config_db['db_password'], $config_db['db_database'], $config_db['db_conn'], $config_db['db_charset'] ); //初始話資料庫類 } /** * 根據表字首獲取表名 * @access final protected * @param string $table_name 表名 */ final protected function table($table_name){ $config_db = $this->config('db'); return $config_db['db_table_prefix'].$table_name; } /** * 載入類庫 * @param string $lib 類庫名稱 * @param Bool $my 如果FALSE預設載入系統自動載入的類庫,如果為TRUE則載入自定義類庫 * @return type */ final protected function load($lib,$my = FALSE){ if(empty($lib)){ trigger_error('載入類庫名不能為空'); }elseif($my === FALSE){ return Application::$_lib[$lib]; }elseif($my === TRUE){ return Application::newLib($lib); } } /** * 載入系統配置,預設為系統配置 $CONFIG['system'][$config] * @access final protected * @param string $config 配置名 */ final protected function config($config=''){ return Application::$_config[$config]; } }
因為模型基本是處理資料庫的相關內容,所以我們載入了mysql類,這個mysql類就不在這裡寫了,你可以自己根據習慣寫自己的mysql的操作類,如果你想支援其他的資料庫,完全可以自己靈活新增。
核心模型控制器已經有了,其實裡面還可以新增其他你覺得必要的全域性函式,這樣我們開始新建一個自己的控制器和模型,來例項運用一下新建controller/testController.php
<?php /** * 測試控制器 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ class testController extends Controller { public function __construct() { parent::__construct(); } public function index() { echo "test"; } public function testDb() { $modTest = $this->model('test'); //示例化test模型 $databases = $modTest->testDatebases(); //呼叫test模型中 testDatebases()方法 var_dump($databases); } }
testController 繼承我們的核心控制器,其實在以後的每個控制器中都要繼承的,現在我們通過瀏覽器訪問 http://localhost/myapp/index.php?controller=test ,哈哈,可以輸出 test 字串了然後我們再新建一個模型model/testModel.php
<?php /** * 測試模型 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ class testModel extends Model{ function testDatabases(){ $this->db->show_databases(); } }
其實就是定義了一個獲取所有的資料庫的方法,開啟瀏覽器訪問 http://localhost/myapp/index.php?controller=test&action=testDb,不管你信不信,反正我的瀏覽器是輸出了所有的資料庫了
現在就差檢視了,其實在核心控制器的controller.php檔案中已經有了一個showTemplate方法,其實就是實現了載入模板類,$data就是我們要傳遞給模板的變數,然後輸出模板
/** * 載入模板檔案 * @access final protect * @param string $path 模板路徑 * @param array $data 模板變數 * @return string 模板字串 */ final protected function showTemplate($path,$data = array()){ $template = $this->load('template'); $template->init($path,$data); $template->outPut(); }
下面我們來看一下template類
<?php /** * 模板類 * @copyright Copyright(c) 2011 * @author yuansir <[email protected]/yuansir-web.com> * @version 1.0 */ final class Template { public $template_name = null; public $data = array(); public $out_put = null; public function init($template_name,$data = array()) { $this->template_name = $template_name; $this->data = $data; $this->fetch(); } /** * 載入模板檔案 * @access public * @param string $file */ public function fetch() { $view_file = VIEW_PATH . '/' . $this->template_name . '.php'; if (file_exists($view_file)) { extract($this->data); ob_start(); include $view_file; $content = ob_get_contents(); ob_end_clean(); $this->out_put = $content; } else { trigger_error('載入 ' . $view_file . ' 模板不存在'); } } /** * 輸出模板 * @access public * @return string */ public function outPut(){ echo $this->out_put; }
是不是簡單,就是引入你的靜態模版檔案,放在緩衝區,然後輸出,其實如果你想靜態化某個模版,那個這個放在緩衝區的$this->out_put就有用了,你可以在裡面新增一個靜態化的方法。好了,現在我們來在新建一個檢視檔案 view/test.php
<html> <body> 這是<?php echo $test; ?>,呵呵 </body> <html> 然後修改一些我們的testController.php中的index() public function index() { $data['test'] = "yuansir-web.com"; $this->showTemplate('test', $data); }
再來瀏覽 http://localhost/myapp/index.php?controller=test ,可以輸出 “這是 http://yuansir-web.com,呵呵”,那麼顯然我們的檢視也完成了。
這樣我們的自己寫PHP的MVC的框架就完成了,再補充一下,有人可能疑惑如果我是想建立前臺後臺的,單一入口怎麼辦呢,其實你要是從頭就看我的這個教程,看下程式碼就會發現,其實只要在 controller目錄下新建一個admin目錄就可以在裡面寫控制器了,比如controller/admin/testController.php 模板引用也是同樣的道理,建立 view/admin/test.php ,然後模板加上路徑就可以了,$this->showTemplate('admin/test', $data);是不是很簡單,很靈活。
好了,這樣我們《自己動手寫PHP MVC框架》的教程就結束了,你可以模仿自己寫一個,也可以根據自己的思路來寫一個,我教程中的可以自己擴增成一個完善的框架,再次申明一下,教程中的程式碼不完善,沒有做過任何基準測試,效率神馬的不考慮,便捷性神馬的看個人,呵呵