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的动作,所以我们只要看后者
 
	/**
	 * 加载函数库
	 * @param string $func 函数库名
	 * @param string $path 地址
	 */
	private static function _load_func($func, $path = "') {
		static $funcs = array();
		if (empty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'functions';
		$path .= DIRECTORY_SEPARATOR.$func.'.func.php';
		$key = md5($path);
		if (isset($funcs[$key])) return true;
		if (file_exists(PC_PATH.$path)) {
			include PC_PATH.$path;
		} else {
			$funcs[$key] = false;
			return false;
		}
		$funcs[$key] = true;
		return true;
	}
 
这个函数在默认情况下是加载libs/functions/目录下面后缀为.fund.php的文件, 如果设置了path参数则去 path目录下加载 $fund.func.php文件
 
下面看下pc_basae::auto_load_func
同样只是做了一个传递参数的动作,我们直接看pc_base::_auto_load_func
 
	/**
	 * 加载函数库
	 * @param string $func 函数库名
	 * @param string $path 地址
	 */
	private static function _auto_load_func($path = "') {
		if (empty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'functions'.DIRECTORY_SEPARATOR.'autoload';
		$path .= DIRECTORY_SEPARATOR.'*.func.php';
		$auto_funcs = glob(PC_PATH.DIRECTORY_SEPARATOR.$path);
		if(!empty($auto_funcs) && is_array($auto_funcs)) {
			foreach($auto_funcs as $func_path) {
				include $func_path;
			}
		}
	}
 
这个函数的作用在默认情况下只是将所以位于lib/functions/autoload/目录下所有以.func.php结尾的文件,如果设置了path目录的话,就去加载所有位于path目录下以.func.php结尾的文件
 
后面v9调用了一个pc_base::load_config,这似乎是加载一个配置文件,来看下
 
 
	/**
	 * 加载配置文件
	 * @param string $file 配置文件
	 * @param string $key  要获取的配置键
	 * @param string $default  默认配置。当获取配置项目失败时该值发生作用。
	 * @param boolean $reload 强制重新加载。
	 */
	public static function load_config($file, $key = "', $default = '', $reload = false) {
		static $configs = array();
		if (!$reload && isset($configs[$file])) {
			if (empty($key)) {
				return $configs[$file];
			} elseif (isset($configs[$file][$key])) {
				return $configs[$file][$key];
			} else {
				return $default;
			}
		}
		$path = CACHE_PATH.'configs'.DIRECTORY_SEPARATOR.$file.'.php';
		if (file_exists($path)) {
			$configs[$file] = include $path;
		}
		if (empty($key)) {
			return $configs[$file];
		} elseif (isset($configs[$file][$key])) {
			return $configs[$file][$key];
		} else {
			return $default;
		}
	}

 

这个函数的作用只是先加载了一下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
 
 
	/**
	 * 加载类文件函数
	 * @param string $classname 类名
	 * @param string $path 扩展地址
	 * @param intger $initialize 是否初始化
	 */
	private static function _load_class($classname, $path = "', $initialize = 1) {
		static $classes = array();
		if (empty($path)) $path = 'libs'.DIRECTORY_SEPARATOR.'classes';

		$key = md5($path.$classname);
		if (isset($classes[$key])) {
			if (!empty($classes[$key])) {
				return $classes[$key];
			} else {
				return true;
			}
		}
		if (file_exists(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {
			include PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php';
			$name = $classname;
			if ($my_path = self::my_path(PC_PATH.$path.DIRECTORY_SEPARATOR.$classname.'.class.php')) {
				include $my_path;
				$name = 'MY_'.$classname;
			}
			if ($initialize) {
				$classes[$key] = new $name;
			} else {
				$classes[$key] = true;
			}
			return $classes[$key];
		} else {
			return false;
		}
	}

 

这个函数默认情况下载入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来载入了控制器类。
 
 
	/**
	 * 加载控制器
	 * @param string $filename
	 * @param string $m
	 * @return obj
	 */
	private function load_controller($filename = "', $m = '') {
		if (empty($filename)) $filename = ROUTE_C;
		if (empty($m)) $m = ROUTE_M;
		$filepath = PC_PATH.'modules'.DIRECTORY_SEPARATOR.$m.DIRECTORY_SEPARATOR.$filename.'.php';
		if (file_exists($filepath)) {
			$classname = $filename;
			include $filepath;
			if ($mypath = pc_base::my_path($filepath)) {
				$classname = 'MY_'.$filename;
				include $mypath;
			}
			if(class_exists($classname)){
				return new $classname;
			}else{
				exit('Controller does not exist.');
 			}
		} else {
			exit('Controller does not exist.');
		}
	}

 

在这个方法里面就是根据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方法。
 
 
	//内容页
	public function show() {
		$catid = intval($_GET["catid']);
		$id = intval($_GET['id']);
                ...

		include template('content',$template);
	}
 
 
暂时忽略这个方法中的业务逻辑,看到最后的部份,发现载入了template这个函数所返回的东西(所在文件: libs/functions/global.func.php)
 
/**
 * 模板调用
 *
 * @param $module
 * @param $template
 * @param $istag
 * @return unknown_type
 */
function template($module = "content', $template = 'index', $style = '') {

	if(strpos($module, 'plugin/')!== false) {
		$plugin = str_replace('plugin/', '', $module);
		return p_template($plugin, $template,$style);
	}
	$module = str_replace('/', DIRECTORY_SEPARATOR, $module);
	if(!empty($style) && preg_match('/([a-z0-9\-_]+)/is',$style)) {
	} elseif (empty($style) && !defined('STYLE')) {
		if(defined('SITEID')) {
			$siteid = SITEID;
		} else {
			$siteid = param::get_cookie('siteid');
		}
		if (!$siteid) $siteid = 1;
		$sitelist = getcache('sitelist','commons');
		if(!empty($siteid)) {
			$style = $sitelist[$siteid]['default_style'];
		}
	} elseif (empty($style) && defined('STYLE')) {
		$style = STYLE;
	} else {
		$style = 'default';
	}
	if(!$style) $style = 'default';
	$template_cache = pc_base::load_sys_class('template_cache');
	$compiledtplfile = PHPCMS_PATH.'caches'.DIRECTORY_SEPARATOR.'caches_template'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.php';
	if(file_exists(PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html')) {
		if(!file_exists($compiledtplfile) || (@filemtime(PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html') > @filemtime($compiledtplfile))) {
			$template_cache->template_compile($module, $template, $style);
		}
	} else {
		$compiledtplfile = PHPCMS_PATH.'caches'.DIRECTORY_SEPARATOR.'caches_template'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.php';
		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))) {
			$template_cache->template_compile($module, $template, 'default');
		} elseif (!file_exists(PC_PATH.'templates'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html')) {
			showmessage('Template does not exist.'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html');
		}
	}
	return $compiledtplfile;
}
 
在这个函数的前半部份做了一个对$style这个变量的赋值。后半部分载入了一个叫做template_cache的类, 然后就是对模板文件的存在情况以及比对模板文件和编译文件的时间戳决定是否需要重新编译。最后返回一个编译文件的路径。
现在来看下template_cache这个类(libs/classes/template_cache.class.php)的template_cache->template_compile方法
 
	/**
	 * 编译模板
	 *
	 * @param $module	模块名称
	 * @param $template	模板文件名
	 * @param $istag	是否为标签模板
	 * @return unknown
	 */
	
	public function template_compile($module, $template, $style = "default') {
		if(strpos($module, '/')=== false) {
		$tplfile = $_tpl = PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html';
		} elseif (strpos($module, 'yp/') !== false) {
			$module = str_replace('/', DIRECTORY_SEPARATOR, $module);
			$tplfile = $_tpl = PC_PATH.'templates'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html';
		} else {
			$plugin = str_replace('plugin/', '', $module);
			$module = str_replace('/', DIRECTORY_SEPARATOR, $module);
			$tplfile = $_tpl = PC_PATH.'plugin'.DIRECTORY_SEPARATOR.$plugin.DIRECTORY_SEPARATOR.'templates'.DIRECTORY_SEPARATOR.$template.'.html';
		}
		if ($style != 'default' && !file_exists ( $tplfile )) {
			$style = 'default';
			$tplfile = PC_PATH.'templates'.DIRECTORY_SEPARATOR.'default'.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.'.html';
		}
		if (! file_exists ( $tplfile )) {
			showmessage ( "templates".DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR.$template.".html is not exists!" );
		}
		$content = @file_get_contents ( $tplfile );

		$filepath = CACHE_PATH.'caches_template'.DIRECTORY_SEPARATOR.$style.DIRECTORY_SEPARATOR.$module.DIRECTORY_SEPARATOR;
	    if(!is_dir($filepath)) {
			mkdir($filepath, 0777, true);
	    }
		$compiledtplfile = $filepath.$template.'.php';
		$content = $this->template_parse($content);
		$strlen = file_put_contents ( $compiledtplfile, $content );
		chmod ( $compiledtplfile, 0777 );
		return $strlen;
	}
 
这个方法还是分成了前后两个部分,前半部分作了一个对$tplfile这个变量的赋值,它代表了模板文件的路径,根据$module, $template, $style变量的不同有所不同,这个部分下次有时间再细讲。有兴趣的可以自己看一下。后半部分是调用了template_cache->template_parse这个方法,并且把它所返回的内容写入了编译文件。实际上这个方法就是模板编译的核心部分。看一下这个方法。
 
 
	/**
	 * 解析模板
	 *
	 * @param $str	模板内容
	 * @return ture
	 */
	public function template_parse($str) {
		$str = preg_replace ( "/\{template\s+(.+)\}/", "<?php include template(\\1); ?>", $str );
		$str = preg_replace ( "/\{include\s+(.+)\}/", "", $str );
		$str = preg_replace ( "/\{php\s+(.+)\}/", "", $str );
		$str = preg_replace ( "/\{if\s+(.+?)\}/", "", $str );
		$str = preg_replace ( "/\{else\}/", "", $str );
		$str = preg_replace ( "/\{elseif\s+(.+?)\}/", "", $str );
		$str = preg_replace ( "/\{\/if\}/", "", $str );
		//for 循环
		$str = preg_replace("/\{for\s+(.+?)\}/","",$str);
		$str = preg_replace("/\{\/for\}/","",$str);
		//++ --
		$str = preg_replace("/\{\+\+(.+?)\}/","",$str);
		$str = preg_replace("/\{\-\-(.+?)\}/","",$str);
		$str = preg_replace("/\{(.+?)\+\+\}/","",$str);
		$str = preg_replace("/\{(.+?)\-\-\}/","",$str);
		$str = preg_replace ( "/\{loop\s+(\S+)\s+(\S+)\}/", "", $str );
		$str = preg_replace ( "/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/", " \\3) { ?>", $str );
		$str = preg_replace ( "/\{\/loop\}/", "", $str );
		$str = preg_replace ( "/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "", $str );
		$str = preg_replace ( "/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "", $str );
		$str = preg_replace ( "/\{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "", $str );
		$str = preg_replace("/\{(\\$[a-zA-Z0-9_\[\]\"\"\$\x7f-\xff]+)\}/es", "\$this->addquote('')",$str);
		$str = preg_replace ( "/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s", "", $str );
		$str = preg_replace("/\{pc:(\w+)\s+([^}]+)\}/ie", "self::pc_tag('$1','$2', '$0')", $str);
		$str = preg_replace("/\{\/pc\}/ie", "self::end_pc_tag()", $str);
		$str = "" . $str;
		return $str;
	}

 

可以发现这个方法里面所有的方法都是对模板文件中的标记进行替换成标准的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
 
 
	/**
	 * 加载数据模型
	 * @param string $classname 类名
	 */
	public static function load_model($classname) {
		return self::_load_class($classname,"model');
	}

 

发现在这个函数中只是对pc_base::_load_class的一个调用,并把pc_base::_load_class的path参数设置了为model,也就是说pc_base::_load_class将会从model目录直接加载类文件。而这里这个文件就是v9的一个模型了。
现在我们在model目录下任意找一个文件看一个它的内容。这里我选择的是admin_model.class.php,因为它是相当简单的一个文件。
 
 
defined("IN_PHPCMS') or exit('No permission resources.');
pc_base::load_sys_class('model', '', 0);
class admin_model extends model {
	public function __construct() {
		$this->db_config = pc_base::load_config('database');
		$this->db_setting = 'default';
		$this->table_name = 'admin';
		parent::__construct();
	}
}

 

 
在这个admin_model类中可以看到这个类其实没有做什么事情,它只是设置了一下它的三个成员变量,然后继承了它的父类model。 很明显,这个model是在第二行由pc_base::load_sys_class载入的,所以它应该会位于libs/classes/这个目录。
现在打开libs/classes/model.php这个文件,大致的看一下方法列表可以发现在model这个类中实际上就是对数据库操作的一系列方法的封装。如此也就是说只要子类继承了model类,就可以实现对数据库的一系列操作了。
 
在二次开发的过程中可能会需要封装自己的模型,那么也只要存放在model这个目录下,继承model类即可。
 
对v9的模型就这样简单介绍一下。接下来会有其它的文章继续v9中如何实现对不同数据库的适应。
 
总结
以上就是web开发网(http://www.zeroplace.cn)对phpcmsv9的代码总体结构的一个简单介绍,接下来将会继续乱解读phpcmsv9的各个部分的代码。有问题的同学请加群。

 



文章来自: 本站原创
Tags:
评论: 0 | 查看次数: 11589