MVC架构-实现
1.目录结构
Project
|-app
| |-controller
| | |-Controller.php
| | |-IndexController.php
| |-model
| | |-IndexModel.php
| |-view
| |-index
| |-page.html
| |-show.html
|-bootstrap
| |-Psr4AutoLoad.php
|-vender
| |-lib
| |-Tpl.php
|-cache
|-config
|-public
|-index.php
app
controller
控制器,根据用户请求的参数进行处理
先通过 model 进行逻辑处理,再通过 模板引擎(上一篇为基础) 和 view 渲染并显示页面
model
模型,对从 controller 传来的数据进行逻辑处理
view
视图,存储 HTML模板文件
bootstrap
Psr4AutoLoad.php 为 Psr4AutoLoad类文件
用于设置 命名空间映射,并在调用类时进行 命名空间映射 然后包含对应 类文件
命名空间映射
通过键值对数组得到 类文件路径(命名空间=>类文件路径)
vender
存储第三方库,这里存储了上一篇讲的 模板引擎类
cache
存储临时文件,这里存储 由HTML模板 生成的 PHP缓存文件
config
存储配置文件
public
存储 js、css 等文件
index.php
用于初始化 Psr4AutoLoad类 并设置 命名空间映射
设置路由,将参数交给 controller 处理
2.代码实现
访问 /index.php?m=index&a=index&id=0 时
先设置了 命名空间映射
然后通过路由机制,由 m 得出控制器名,由 a 得出要调用的控制器方法,进而调用 IndexController类 的 index方法
index方法 调用 IndexModel类 的 newId方法 对 id 进行逻辑处理,然后调用 Tpl模板引擎类 的方法通过 view 中的 HTML模板 生成 PHP缓存文件,并包含显示页面
index.php
<?php
include 'bootstrap/Psr4AutoLoad.php';
$psr = new Psr4AutoLoad();
// 设置命名空间映射
$psr->addMaps('controller', 'app/controller/');
$psr->addMaps('model', 'app/model/');
$psr->addMaps('framework', 'vender/lib/');
// 路由
// 获取控制器名,controller\IndexController
$m = $_GET['m'];
$className = 'controller\\'.ucfirst(strtolower($m)).'Controller';
// 获取方法名
$a = $_GET['a'];
// 调用控制器的方法
$obj = new $className();
call_user_func([$obj, $a]);
Psr4AutoLoad.php
<?php
class Psr4AutoLoad {
// 命名空间映射的数组
protected $maps = [];
function __construct() {
// 注册类文件自动加载函数
spl_autoload_register([$this, 'autoload']);
}
// 类文件自动加载函数
function autoload($className) {
// $className:controller\IndexController
// 获取真正类名和命名空间名
$pos = strrpos($className, '\\');
$realClass = substr($className, $pos+1);
$namespace = substr($className, 0,$pos);
// 获取类文件名
$filePath = $this->mapLoad($namespace, $realClass);
include $filePath;
}
// 获取类文件名
protected function mapLoad($namespace, $realClass) {
// 进行命名空间映射
$namespace = $this->maps[$namespace];
return $namespace.$realClass.'.php';
}
// 设置命名空间映射
function addMaps($namespace, $path) {
$this->maps[$namespace] = $path;
}
}
IndexController.php
<?php
namespace controller;
use \model\IndexModel;
class IndexController extends Controller {
function index() {
// 数据处理
$id = IndexModel::newId($_GET['id']);
// 设置变量
$this->assign('title', $id);
$this->assign('show', 'HI,HACKER!');
$this->assign('data', ['HACK', 'ME']);
// 显示页面
$this->display('page.html', true);
}
}
Controller.php
<?php
namespace controller;
use \framework\Tpl;
class Controller extends Tpl{
}
IndexModel.php
<?php
namespace model;
class IndexModel {
static function newId($id){
return $id+1;
}
}
Tpl.php
<?php
namespace framework;
class Tpl {
// HTML模板文件的路径
protected $viewDir = './app/view/index/';
// 生成的PHP缓存文件的路径
protected $cacheDir = './cache/';
// PHP缓存文件的过期时间
protected $lifetime = 3600;
// 存放变量的数组
protected $vars = [];
// 存放变量
function assign($name, $value) {
$this->vars[$name] = $value;
}
/*
* 显示PHP缓存文件
* 参数1:HTML模板文件名
* 参数2:HTML模板文件是否需要包含
*/
function display($viewName, $isInclude) {
// 模板文件全路径
$viewPath = $this->viewDir.$viewName;
// 缓存文件全路径
$cahePath = $this->cacheDir.$viewName.'.php';
// PHP缓存文件不存在则生成PHP缓存文件
if (!file_exists($cahePath)) {
$php = $this->compile($viewPath);
file_put_contents($cahePath, $php);
}
// PHP缓存文件超时则需要重新生成PHP缓存文件
else {
$isTimeout = (filectime($cahePath) + $this->lifetime) > time() ? false : true;
if ($isTimeout) {
$php = $this->compile($viewPath);
file_put_contents($cahePath, $php);
}
}
// 需要包含则解析变量并包含PHP缓存文件
if ($isInclude) {
extract($this->vars);
include $cahePath;
}
}
// 生成PHP缓存文件
protected function compile($viewPath) {
// 获取HTML模板文件内容
$html = file_get_contents($viewPath);
// \1为第一个捕获组匹配到的内容
$array = [
'{$%%}'=>'<?=$\1;?>',
'{foreach %%}'=>'<?php foreach (\1):?>',
'{/foreach}'=>'<?php endforeach;?>',
'{include %%}'=>''
];
foreach ($array as $key=>$value) {
/*
* 转义并生成正则
* #包裹表示正则的/不需要转义
* (.+?)表示一个懒惰匹配的捕获组
*/
$pattern = '#'.str_replace('%%', '(.+?)', preg_quote($key)).'#';
// 对HTML模板文件内容进行正则替换,include特殊情况要用回调函数处理
if (strstr($pattern, 'include')) {
$html = preg_replace_callback($pattern, [$this, 'parseInclude'], $html);
}
else {
$html = preg_replace($pattern, $value, $html);
}
}
return $html;
}
// 处理include,生成要包含进来的HTML模板文件的PHP缓存文件
protected function parseInclude($data) {
/*
* 生成要包含进来的HTML模板文件的PHP缓存文件
* $data[0]为匹配到的{include show.html}
* $data[1]为第一个捕获组匹配到的show.html
*/
$this->display($data[1], false);
$cachePath = $this->cacheDir.$data[1].'.php';
return '<?php include "'.$cachePath.'";?>';
}
}
page.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{$title}</title>
</head>
<body>
{include show.html}
{foreach $data as $value}
{$value}<br>
{/foreach}
</body>
</html>
show.html
<div>{$show}</div>