PHPCMSV9 亂解讀 之 PHPCMS V9的MVC

PHPCMSV9 亂解讀 之 PHPCMS V9的MVC 

 

標 題: PHPCMSV9 亂解讀 之 PHPCMS V9的MVC
作 者: web開發網[http://www.zeroplace.cn]
時 間: 2013/07/07 21:15:00
鏈 接: http://www.zeroplace.cn/article.asp?id=853
說 明: 轉載請保留本段文字 

 現在的程序設計基本上都講求MVC,數據與模板分離,單一入口。

這篇文章準先寫一寫PHPCMSV9的運行和它的MVC是怎麼實現的!
 
(習慣問題我喜歡將所有的方法稱之爲函數,所以下面我如果提到函數的話很可能就是某個類的一個方法)
 
1. 入口文件
index.php是v9的入口文件,這是文件裏面只是做了一個pc_base::create_app()的調用 
 
2. pc_base中的各種文件載入機制
到pc_base文件裏面的時候發現這個文件首先調用了pc_base的兩個方法(pc_base::load_sys_fund, pc_base::auto_load_func),加載了一些什麼東西
pc_base::load_sys_func 只是把做了一個把參數傳遞給pc_base::_load_func的動作,所以我們只要看後者
 
  1. /** 
  2.  * 加載函數庫 
  3.  * @param string $func 函數庫名 
  4.  * @param string $path 地址 
  5.  */  
  6. private static function _load_func($func$path = "') {  
  7.     static $funcs = array();  
  8.     if (emptyempty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'functions';  
  9.     $path .= DIRECTORY_SEPARATOR.$func.'.func.php';  
  10.     $key = md5($path);  
  11.     if (isset($funcs[$key])) return true;  
  12.     if (file_exists(PC_PATH.$path)) {  
  13.         include PC_PATH.$path;  
  14.     } else {  
  15.         $funcs[$key] = false;  
  16.         return false;  
  17.     }  
  18.     $funcs[$key] = true;  
  19.     return true;  
  20. }  
 
這個函數在默認情況下是加載libs/functions/目錄下面後綴爲.fund.php的文件, 如果設置了path參數則去 path目錄下加載 $fund.func.php文件
 
下面看下pc_basae::auto_load_func
同樣只是做了一個傳遞參數的動作,我們直接看pc_base::_auto_load_func
 
  1. /** 
  2.  * 加載函數庫 
  3.  * @param string $func 函數庫名 
  4.  * @param string $path 地址 
  5.  */  
  6. private static function _auto_load_func($path = "') {  
  7.     if (emptyempty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'functions'.DIRECTORY_SEPARATOR.'autoload';  
  8.     $path .= DIRECTORY_SEPARATOR.'*.func.php';  
  9.     $auto_funcs = glob(PC_PATH.DIRECTORY_SEPARATOR.$path);  
  10.     if(!emptyempty($auto_funcs) && is_array($auto_funcs)) {  
  11.         foreach($auto_funcs as $func_path) {  
  12.             include $func_path;  
  13.         }  
  14.     }  
  15. }  
 
這個函數的作用在默認情況下只是將所以位於lib/functions/autoload/目錄下所有以.func.php結尾的文件,如果設置了path目錄的話,就去加載所有位於path目錄下以.func.php結尾的文件
 
後面v9調用了一個pc_base::load_config,這似乎是加載一個配置文件,來看下
 
 
  1. /** 
  2.  * 加載配置文件 
  3.  * @param string $file 配置文件 
  4.  * @param string $key  要獲取的配置鍵 
  5.  * @param string $default  默認配置。當獲取配置項目失敗時該值發生作用。 
  6.  * @param boolean $reload 強制重新加載。 
  7.  */  
  8. public static function load_config($file$key = "', $default = '', $reload = false) {  
  9.     static $configs = array();  
  10.     if (!$reload && isset($configs[$file])) {  
  11.         if (emptyempty($key)) {  
  12.             return $configs[$file];  
  13.         } elseif (isset($configs[$file][$key])) {  
  14.             return $configs[$file][$key];  
  15.         } else {  
  16.             return $default;  
  17.         }  
  18.     }  
  19.     $path = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.$file.'.php';  
  20.     if (file_exists($path)) {  
  21.         $configs[$file] = include $path;  
  22.     }  
  23.     if (emptyempty($key)) {  
  24.         return $configs[$file];  
  25.     } elseif (isset($configs[$file][$key])) {  
  26.         return $configs[$file][$key];  
  27.     } else {  
  28.         return $default;  
  29.     }  
  30. }  

 

這個函數的作用只是先加載了一下CACHE_PATH/config/的.php結尾的文件,並獲取了文件中return的值。這裏v9默認設置了CACHE目錄爲根目錄下的 cache目錄看一下里面的system.php文件後發現裏面只是返回了一個關聯數組。其實這就是作爲v9的配置文件了。
 
隨後v9就進入了create_app的調用了,從index.php來看發現這個函數是最外層的最後一次調用,也就是說,後面所有的操作都在這個文件裏面完成了。到這個函數裏面的時候發現它只是調用了一個pc_base::load_sys_class的函數,而後者只是將參數傳遞給了pc_base::_load_class
 
 
  1. /** 
  2.  * 加載類文件函數 
  3.  * @param string $classname 類名 
  4.  * @param string $path 擴展地址 
  5.  * @param intger $initialize 是否初始化 
  6.  */  
  7. private static function _load_class($classname$path = "', $initialize = 1) {  
  8.     static $classes = array();  
  9.     if (emptyempty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'classes';  
  10.   
  11.     $key = md5($path.$classname);  
  12.     if (isset($classes[$key])) {  
  13.         if (!emptyempty($classes[$key])) {  
  14.             return $classes[$key];  
  15.         } else {  
  16.             return true;  
  17.         }  
  18.     }  
  19.     if (file_exists(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {  
  20.         include PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php';  
  21.         $name = $classname;  
  22.         if ($my_path = self::my_path(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {  
  23.             include $my_path;  
  24.             $name = 'MY_'.$classname;  
  25.         }  
  26.         if ($initialize) {  
  27.             $classes[$key] = new $name;  
  28.         } else {  
  29.             $classes[$key] = true;  
  30.         }  
  31.         return $classes[$key];  
  32.     } else {  
  33.         return false;  
  34.     }  
  35. }  

 

這個函數默認情況下載入libs/classes/目錄下後綴爲.class.php的文件,並實例化之。這裏v9提供了一個方法來擴展這目錄裏面的這些類文件,就是在這個目錄下再定義一個MY_的類,文件名也和類名保持一致就可以了。v9會自動實例化加MY_的類
 
3. MVC模式
3.1 控制器的載入及調用(Controller)
實例化application之後就進入了真正的程序邏輯部分了。 在application的構造函數部分可以看到實例化了一個param類,定義了三個常量(就是v9的module, controller, action), 隨後調用了application->init方法。
 
我們來到libs/classes/param.class.php 看一下這個param類。
先看下它的構造函數部分。首先對$_POST,$_GET,$_COOKIE,$_REQUEST四個變量做了addslashes處理。 然後載入了一個叫route.php的配置文件, 對$_POST和$_GET裏面的一些值做默認值處理。再看到接下來的三個成員方法: param->route_m, param->route_c, param->a。 發現只是對$_GET或$_POST中的m, a, c 三個鍵的獲取,如果沒有設置的話,根據route.php配置設置默認值。
看到這裏其實就知道,這個param類是作爲一個route來用的,如果你什麼時候需要改一下v9的url規則,就可以從這裏入手了。 關於param的其它介紹下次用到的時候再寫。
 
回到application類, 定義了ROUTE_M, ROUTE_C, ROUTE_A之後就進入了application->init方法。在這個方法中首先調用了application->load_controller來載入了控制器類。
 
 
  1. /** 
  2.  * 加載控制器 
  3.  * @param string $filename 
  4.  * @param string $m 
  5.  * @return obj 
  6.  */  
  7. private function load_controller($filename = "', $m = '') {  
  8.     if (emptyempty($filename)) $filename = ROUTE_C;  
  9.     if (emptyempty($m)) $m = ROUTE_M;  
  10.     $filepath = PC_PATH.'modules'.DIRECTORY_SEPARATOR.$m.DIRECTORY_SEPARATOR.$filename.'.php';  
  11.     if (file_exists($filepath)) {  
  12.         $classname = $filename;  
  13.         include $filepath;  
  14.         if ($mypath = pc_base::my_path($filepath)) {  
  15.             $classname = 'MY_'.$filename;  
  16.             include $mypath;  
  17.         }  
  18.         if(class_exists($classname)){  
  19.             return new $classname;  
  20.         }else{  
  21.             exit('Controller does not exist.');  
  22.             }  
  23.     } else {  
  24.         exit('Controller does not exist.');  
  25.     }  
  26. }  

 

在這個方法裏面就是根據ROUTE_M, ROUTE_C載入了 modules/ROUTE_M/ROUTE_C.php這個控制器類所在的文件。 同樣的這裏也支持上面所說的MY_擴展。
 
接下來就是對控制器中ROUTE_A這個方法的調用了。這就是v9的MVC模式中C的實現部分。
 
3.2 模板的編譯及調用(View)
現在可以找一個v9中的地址來看一下。例如: 
通過閱讀上面3.1的內容,可以很容易找到這個地址所對應的Controller爲modules/content/index.php 中的 index->show方法。
 
 
  1. //內容頁  
  2. public function show() {  
  3.     $catid = intval($_GET["catid']);  
  4.     $id = intval($_GET['id']);  
  5.                ...  
  6.   
  7.     include template('content',$template);  
  8. }  
 
 
暫時忽略這個方法中的業務邏輯,看到最後的部份,發現載入了template這個函數所返回的東西(所在文件: libs/functions/global.func.php)
 
  1. /** 
  2.  * 模板調用 
  3.  * 
  4.  * @param $module 
  5.  * @param $template 
  6.  * @param $istag 
  7.  * @return unknown_type 
  8.  */  
  9. function template($module = "content', $template = 'index', $style = '') {  
  10.   
  11.     if(strpos($module'plugin/')!== false) {  
  12.         $plugin = str_replace('plugin/'''$module);  
  13.         return p_template($plugin$template,$style);  
  14.     }  
  15.     $module = str_replace('/', DIRECTORY_SEPARATOR, $module);  
  16.     if(!emptyempty($style) && preg_match('/([a-z0-9\-_]+)/is',$style)) {  
  17.     } elseif (emptyempty($style) && !defined('STYLE')) {  
  18.         if(defined('SITEID')) {  
  19.             $siteid = SITEID;  
  20.         } else {  
  21.             $siteid = param::get_cookie('siteid');  
  22.         }  
  23.         if (!$siteid$siteid = 1;  
  24.         $sitelist = getcache('sitelist','commons');  
  25.         if(!emptyempty($siteid)) {  
  26.             $style = $sitelist[$siteid]['default_style'];  
  27.         }  
  28.     } elseif (emptyempty($style) && defined('STYLE')) {  
  29.         $style = STYLE;  
  30.     } else {  
  31.         $style = 'default';  
  32.     }  
  33.     if(!$style$style = 'default';  
  34.     $template_cache = pc_base::load_sys_class('template_cache');  
  35.     $compiledtplfile = PHPCMS_PATH.'caches'.DIRECTORY_SEPARATOR.'caches_template'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.php';  
  36.     if(file_exists(PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html')) {  
  37.         if(!file_exists($compiledtplfile) || (@filemtime(PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html') > @filemtime($compiledtplfile))) {  
  38.             $template_cache->template_compile($module$template$style);  
  39.         }  
  40.     } else {  
  41.         $compiledtplfile = PHPCMS_PATH.'caches'.DIRECTORY_SEPARATOR.'caches_template'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.php';  
  42.         if(!file_exists($compiledtplfile) || (file_exists(PC_PATH.'templates'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html') && filemtime(PC_PATH.'templates'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html') > filemtime($compiledtplfile))) {  
  43.             $template_cache->template_compile($module$template'default');  
  44.         } elseif (!file_exists(PC_PATH.'templates'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html')) {  
  45.             showmessage('Template does not exist.'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html');  
  46.         }  
  47.     }  
  48.     return $compiledtplfile;  
  49. }  
 
在這個函數的前半部份做了一個對$style這個變量的賦值。後半部分載入了一個叫做template_cache的類, 然後就是對模板文件的存在情況以及比對模板文件和編譯文件的時間戳決定是否需要重新編譯。最後返回一個編譯文件的路徑。
現在來看下template_cache這個類(libs/classes/template_cache.class.php)的template_cache->template_compile方法
 
  1. /** 
  2.  * 編譯模板 
  3.  * 
  4.  * @param $module   模塊名稱 
  5.  * @param $template 模板文件名 
  6.  * @param $istag    是否爲標籤模板 
  7.  * @return unknown 
  8.  */  
  9.   
  10. public function template_compile($module$template$style = "default') {  
  11.     if(strpos($module'/')=== false) {  
  12.     $tplfile = $_tpl = PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html';  
  13.     } elseif (strpos($module'yp/') !== false) {  
  14.         $module = str_replace('/', DIRECTORY_SEPARATOR, $module);  
  15.         $tplfile = $_tpl = PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html';  
  16.     } else {  
  17.         $plugin = str_replace('plugin/'''$module);  
  18.         $module = str_replace('/', DIRECTORY_SEPARATOR, $module);  
  19.         $tplfile = $_tpl = PC_PATH.'plugin'.DIRECTORY_SEPARATOR.$plugin.DIRECTORY_SEPARATOR.'templates'.DIRECTORY_SEPARATOR.$template.'.html';  
  20.     }  
  21.     if ($style != 'default' && !file_exists ( $tplfile )) {  
  22.         $style = 'default';  
  23.         $tplfile = PC_PATH.'templates'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html';  
  24.     }  
  25.     if (! file_exists ( $tplfile )) {  
  26.         showmessage ( "templates".DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.".html is not exists!" );  
  27.     }  
  28.     $content = @file_get_contents ( $tplfile );  
  29.   
  30.     $filepath = CACHE_PATH.'caches_template'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR;  
  31.     if(!is_dir($filepath)) {  
  32.         mkdir($filepath, 0777, true);  
  33.     }  
  34.     $compiledtplfile = $filepath.$template.'.php';  
  35.     $content = $this->template_parse($content);  
  36.     $strlen = file_put_contents ( $compiledtplfile$content );  
  37.     chmod ( $compiledtplfile, 0777 );  
  38.     return $strlen;  
  39. }  
 
這個方法還是分成了前後兩個部分,前半部分作了一個對$tplfile這個變量的賦值,它代表了模板文件的路徑,根據$module, $template, $style變量的不同有所不同,這個部分下次有時間再細講。有興趣的可以自己看一下。後半部分是調用了template_cache->template_parse這個方法,並且把它所返回的內容寫入了編譯文件。實際上這個方法就是模板編譯的核心部分。看一下這個方法。
 
 
  1. /** 
  2.  * 解析模板 
  3.  * 
  4.  * @param $str  模板內容 
  5.  * @return ture 
  6.  */  
  7. public function template_parse($str) {  
  8.     $str = preg_replace ( "/\{template\s+(.+)\}/""<?php include template(\\1); ?>"$str );  
  9.     $str = preg_replace ( "/\{include\s+(.+)\}/""<!--p include \\1;-->"$str );  
  10.     $str = preg_replace ( "/\{php\s+(.+)\}/""<!--p \\-->"$str );  
  11.     $str = preg_replace ( "/\{if\s+(.+?)\}/""<!--p if(\\1) {-->"$str );  
  12.     $str = preg_replace ( "/\{else\}/""<!--p } else {-->"$str );  
  13.     $str = preg_replace ( "/\{elseif\s+(.+?)\}/""<!--p } elseif (\\1) {-->"$str );  
  14.     $str = preg_replace ( "/\{\/if\}/""<!--p }-->"$str );  
  15.     //for 循環  
  16.     $str = preg_replace("/\{for\s+(.+?)\}/","<!--p for(\\1) {-->",$str);  
  17.     $str = preg_replace("/\{\/for\}/","<!--p }-->",$str);  
  18.     //++ --  
  19.     $str = preg_replace("/\{\+\+(.+?)\}/","<!--p ++\\1;-->",$str);  
  20.     $str = preg_replace("/\{\-\-(.+?)\}/","<!--p ++\\1;-->",$str);  
  21.     $str = preg_replace("/\{(.+?)\+\+\}/","<!--p \\1++;-->",$str);  
  22.     $str = preg_replace("/\{(.+?)\-\-\}/","",$str);  
  23.     $str = preg_replace ( "/\{loop\s+(\S+)\s+(\S+)\}/""<!--p \$n=1;if(is_array(\\1)) foreach(\\1 AS \\2) {-->"$str );  
  24.     $str = preg_replace ( "/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/""<!--p \$n=1; if(is_array(\\1)) foreach(\\1 AS \\2--> \\3) { ?>"$str );  
  25.     $str = preg_replace ( "/\{\/loop\}/""<!--p \$n++;}unset(\$n);-->"$str );  
  26.     $str = preg_replace ( "/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/""<!--p echo \\1-->"$str );  
  27.     $str = preg_replace ( "/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/""<!--p echo \\1-->"$str );  
  28.     $str = preg_replace ( "/\{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/""<!--p echo \\1-->"$str );  
  29.     $str = preg_replace("/\{(\\$[a-zA-Z0-9_\[\]\"\"\$\x7f-\xff]+)\}/es""\$this->addquote('<!--p echo \\1-->')",$str);  
  30.     $str = preg_replace ( "/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s""<!--p echo \\1-->"$str );  
  31.     $str = preg_replace("/\{pc:(\w+)\s+([^}]+)\}/ie""self::pc_tag('$1','$2', '$0')"$str);  
  32.     $str = preg_replace("/\{\/pc\}/ie""self::end_pc_tag()"$str);  
  33.     $str = "<!--p defined('IN_PHPCMS') or exit('No permission resources.');-->" . $str;  
  34.     return $str;  
  35. }  

 

可以發現這個方法裏面所有的方法都是對模板文件中的標記進行替換成標準的php語法。這裏所能看到的就是所有v9裏面支持的模板標籤了。如果你想要增加一些標籤的話也可以直接擴展這個方法。
 
看到這裏就明白了v9中模板的實現了,我們把這個過程反過來看一下
首先在template_cache->template_parse中對模板中的標籤進行替換成標準的php語法
在template_cache->template_compile中將這些替換過的內容寫入了編譯文件。
在template這個函數中返回了這個編譯文件的路徑。
最後在Controller中直接include進來這個編譯文件。
 
3.3 模型的實現
細心的同學可以發現我們在講base.php中的pc_base類的時候,漏講了一個函數pc_base::load_model
 
 
  1. /** 
  2.  * 加載數據模型 
  3.  * @param string $classname 類名 
  4.  */  
  5. public static function load_model($classname) {  
  6.     return self::_load_class($classname,"model');  
  7. }  

 

發現在這個函數中只是對pc_base::_load_class的一個調用,並把pc_base::_load_class的path參數設置了爲model,也就是說pc_base::_load_class將會從model目錄直接加載類文件。而這裏這個文件就是v9的一個模型了。
現在我們在model目錄下任意找一個文件看一個它的內容。這裏我選擇的是admin_model.class.php,因爲它是相當簡單的一個文件。
 
 
  1. defined("IN_PHPCMS') or exit('No permission resources.');  
  2. pc_base::load_sys_class('model''', 0);  
  3. class admin_model extends model {  
  4.     public function __construct() {  
  5.         $this->db_config = pc_base::load_config('database');  
  6.         $this->db_setting = 'default';  
  7.         $this->table_name = 'admin';  
  8.         parent::__construct();  
  9.     }  
  10. }  

 

 
在這個admin_model類中可以看到這個類其實沒有做什麼事情,它只是設置了一下它的三個成員變量,然後繼承了它的父類model。 很明顯,這個model是在第二行由pc_base::load_sys_class載入的,所以它應該會位於libs/classes/這個目錄。
現在打開libs/classes/model.php這個文件,大致的看一下方法列表可以發現在model這個類中實際上就是對數據庫操作的一系列方法的封裝。如此也就是說只要子類繼承了model類,就可以實現對數據庫的一系列操作了。
 
在二次開發的過程中可能會需要封裝自己的模型,那麼也只要存放在model這個目錄下,繼承model類即可。
 
對v9的模型就這樣簡單介紹一下。接下來會有其它的文章繼續v9中如何實現對不同數據庫的適應。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章