自己動手寫PHP MVC框架
來自:yuansir-web.com / yuansir@live.cn?
代碼下載: https://github.com/yuansir/tiny-php-framework
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 <yuansir@live.cn/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 <yuansir@live.cn/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為保存為可執行文件 );
用php做一個簡單的網頁、?
我這里有意識的定義$CONFIG['system']數組表示是系統的配置文件,當然你可以在里面定義$CONFIG['myconfig']為表示在定義的配置,以后在程序的控制器,模型,視圖中來調用,都個很自由。
具體配置值代表什么意思注視很清楚了,下面的如果程序中有詳細注釋的我就不解釋啦,呵呵
再來看一下system/app.php文件,主要是干嘛的:
<?php /*** 應用驅動類* @copyright Copyright(c) 2011* @author yuansir <yuansir@live.cn/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; }//初始化cacheif(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
<?phpclass MyTest {function __construct() {echo "my lib test";}}
為什么類名這樣命名,看下newLib方法的實現就知道,其實這些你完全可以定義自己的規則,這個方法會首先去著lib下面有沒有這個類,如果有就會引入實例化,如果沒有就去找系統目錄下面的類,有就實例化
5.init 就是一個初始化的方法,里面其實就是加載自動加載的類,以及引入核心控制器和核心模型,這個2個核心文件過會我們再來分析
6.run 方法就是啟動這個框架的了,里面的最后2步很重要,就是獲取URL然后拆分成一個數組的形似,然后由routeToCm來分發到Controller和Model
7.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 <yuansir@live.cn/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 <yuansir@live.cn/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 <yuansir@live.cn/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 <yuansir@live.cn/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 <yuansir@live.cn/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 <yuansir@live.cn/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);}
mvc框架怎么建立、 再來瀏覽 http://localhost/myapp/index.php?controller=test ,可以輸出 “這是 yuansir-web.com,呵呵”,那么顯然我們的視圖也完成了。
這樣我們的自己寫PHP的MVC的框架就完成了,再補充一下,有人可能疑惑如果我是想建立前臺后臺的,單一入口怎么辦呢,其實你要是從頭就看我的這個教程,看下代碼就會發現,其實只要在 controller目錄下新建
一個admin目錄就可以在里面寫控制器了,比如controller/admin/testController.php 模板引用也是同樣的道理,建立 view/admin/test.php ,然后模板加上路徑就可以了,$this->showTemplate('admin/test', $data);
是不是很簡單,很靈活。
好了,這樣我們《自己動手寫PHP MVC框架》的教程就結束了,你可以模仿自己寫一個,也可以根據自己的思路來寫一個,我教程中的可以自己擴增成一個完善的框架,再次申明一下,教程中的代碼不完善,沒有做過任何基準測試,效率神馬的不考慮,便捷性神馬的看個人,呵呵
?
摘自:http://my.oschina.net/richardx/blog/82016