最近进行代码审计的时候发现传统的cms已经变得极少数了,现在市面上大多数的cms都是mvc架构模式,故此特意去学习一下MVC模式。 ——前言
0x01 概念
MVC模式(Model-View-Controller)是一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)、控制器(Controller)。
视图(View)
用户交互界面,对于WEB应用来说可看为HTML界面。
功能:
1. 显示模型状态
2. 接受数据更新请求
3. 把用户输入数据传给控制器
模型(Model)
业务流程/状态的处理以及业务规则的制定。模型接受视图请求数据,并返回最终的处理结果
功能:
1. 代表应用程序的状态
2. 响应状态查询
3. 处理业务流程
4. 通知视图业务状态
控制(Controller)
从用户接收请求,将模型和视图匹配在一起,共同完成用户的请求。其可以理解为一个分发器,选择什么样的模型,选择什么样的视图。
功能:
1. 接受用户请求
2. 调用模型响应用户请求
3. 选择视图显示响应结果
0x02 流程
- Controller截获用户发出的请求
- Controller调用Model完成状态的读写操作
- Controller把数据传递给View
- View渲染最终结果并呈现给用户
0x03目录结构
- application(应用代码)
- config(程序配置或数据库配置)
- xxxcms(框架核心目录)
- public(静态文件)
- runtime(临时数据目录)
- script(命令行工具)
一般还会在目录下建一个.htaccess文件
<IfModule mod_rewrite.c>
RewriteEngine On
# 确保请求路径不是一个文件名或目录
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# 重定向所有请求到 index.php?url=PATHNAME
RewriteRule ^(.*)$ index.php?url=$1 [PT,L]
</IfModule>
目的是:
1. 除静态程序,所有程序数据都重定向到index.php上
2. 程序有一个单一入口
入口文件
即public目录下的index.php文件
<?php
// 应用目录为当前目录
define('APP_PATH', __DIR__.'/');
// 开启调试模式
define('APP_DEBUG', true);
// 网站根URL
define('APP_URL', 'http://localhost/xxxcms');
// 加载框架
require './xxxcms/xxxPHP.php';
框架文件
即入口文件中加载的xxxPHP.php
<?php
// 初始化常量
defined('FRAME_PATH') or define('FRAME_PATH', __DIR__.'/');
defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
defined('APP_DEBUG') or define('APP_DEBUG', false);
defined('CONFIG_PATH') or define('CONFIG_PATH', APP_PATH.'config/');
defined('RUNTIME_PATH') or define('RUNTIME_PATH', APP_PATH.'runtime/');
// 包含配置文件
require APP_PATH . 'config/config.php';
//包含核心框架类
require FRAME_PATH . 'Core.php';
// 实例化核心类
$fast = new Core;
$fast->run();
核心框架
- 路由处理:
function Route()
{
$controllerName = 'Index';
$action = 'index';
if (!empty($_GET['url'])) {
$url = $_GET['url'];
$urlArray = explode('/', $url);
// 获取控制器名
$controllerName = ucfirst($urlArray[0]);
// 获取动作名
array_shift($urlArray);
$action = empty($urlArray[0]) ? 'index' : $urlArray[0];
//获取URL参数
array_shift($urlArray);
$queryString = empty($urlArray) ? array() : $urlArray;
}
- 敏感字符处理:
// 删除敏感字符
function stripSlashesDeep($value)
{
$value = is_array($value) ? array_map('stripSlashesDeep', $value) : stripslashes($value);
return $value;
}
// 检测敏感字符并删除
function removeMagicQuotes()
{
if ( get_magic_quotes_gpc()) {
$_GET = stripSlashesDeep($_GET );
$_POST = stripSlashesDeep($_POST );
$_COOKIE = stripSlashesDeep($_COOKIE);
$_SESSION = stripSlashesDeep($_SESSION);
}
}
- 自动加载控制器和模型类:
- // 自动加载控制器和模型类
static function loadClass($class)
{
$frameworks = FRAME_PATH . $class . '.class.php';
$controllers = APP_PATH . 'application/controllers/' . $class . '.class.php';
$models = APP_PATH . 'application/models/' . $class . '.class.php';
if (file_exists($frameworks)) {
// 加载框架核心类
include $frameworks;
} elseif (file_exists($controllers)) {
// 加载应用控制器类
include $controllers;
} elseif (file_exists($models)) {
//加载应用模型类
include $models;
} else {
/* 错误代码 */
}
}
控制器基类文件
Controller.class.php的功能是调度其他两层。实现所有控制器、模型和视图(View类)的通信。
<?php /**
* 控制器基类
*/
class Controller
{
protected $_controller;
protected $_action;
protected $_view;
// 构造函数,初始化属性,并实例化对应模型
function __construct($controller, $action)
{
$this->_controller = $controller;
$this->_action = $action;
$this->_view = new View($controller, $action);
}
// 分配变量
function assign($name, $value)
{
$this->_view->assign($name, $value);
}
// 渲染视图
function __destruct()
{
$this->_view->render();
}
}
模型基类
Model.class.php
<?php
class Model extends Sql
{
protected $_model;
protected $_table;
function __construct()
{
// 连接数据库
$this->connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
// 获取模型名称
$this->_model = get_class($this);
$this->_model = rtrim($this->_model, 'Model');
// 数据库表名与类名一致
$this->_table = strtolower($this->_model);
}
function __destruct()
{
}
}
由于模型基类是对数据库进行处理,所以要另建一个Sql.class.php来编写对数据库的操作
具体代码就不给出了,主要是对数据库的增删改查功能。
视图基类
View.class.php
<?php/**
* 视图基类
*/class View
{
protected $variables = array();
protected $_controller;
protected $_action;
function __construct($controller, $action)
{
$this->_controller = $controller;
$this->_action = $action;
}
/** 分配变量 **/
function assign($name, $value)
{
$this->variables[$name] = $value;
}
/** 渲染显示 **/
function render()
{
extract($this->variables);
$defaultHeader = APP_PATH . 'application/views/header.php';
$defaultFooter = APP_PATH . 'application/views/footer.php';
$controllerHeader = APP_PATH . 'application/views/' . $this->_controller . '/header.php';
$controllerFooter = APP_PATH . 'application/views/' . $this->_controller . '/footer.php';
// 页头文件
if (file_exists($controllerHeader)) { include ($controllerHeader);
} else { include ($defaultHeader);
} // 页内容文件
include (APP_PATH . 'application/views/' . $this->_controller . '/' . $this->_action . '.php');
// 页脚文件
if (file_exists($controllerFooter)) { include ($controllerFooter);
} else { include ($defaultFooter);
}
}
}
配置文件
即config目录下的config.php
<?php
/** 变量配置 **/
define('DB_NAME', 'test');
define('DB_USER', 'root');
define('DB_PASSWORD', 'root');
define('DB_HOST', 'localhost');
...
主要都是数据库的配置,不过绝大多数cms中还会有一些对程序的配置
0x04 URL处理
现在大多数mvc模式的cms对url的处理都是用PATHINFO模式(index.php/index/index)
应用的访问方式都是采用单一入口的访问方式,所以访问一个应用中的具体模块及模块中的某个操作,都需要在url中通过入口文件参数进行访问和执行
格式:
http://localhost/入口文件/模块名/操作名/参数1/值1
例如:访问用户模块(user),再去执行添加操作(add)
https://localhost/index.php/user/add
0x05 MVC设计模式优缺点
- 优点:
- 三个层各施其职,互不干涉
- 由于按层把系统分开,那么就能更好的实现开发中的分工。
- 分层后更有利于组件的重用。
- 缺点:
- 增加了系统结构和实现的复杂性。
对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。 - 视图与控制器间的过于紧密的连接。
视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。 - 视图对模型数据的低效率访问。
依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
参考:《细说PHP》