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>
posted @ 2023-04-25 10:22  Hacker&Cat  阅读(38)  评论(0编辑  收藏  举报