完整实现PHP框架(1)

想要完整实现一个完整的PHP框架,并且依靠这个框架搭建出一个良好的,耦合小,可扩展,易迭代的网站应该是每个phper想要做的事情,现在我就推出这款php框架,我取名为ppf(powerful php framework),(我的个人网站 就是使用这个框架搭建的,先介绍他的几点功能

  • MVC结构设计
  • 模版引擎功能的实现
  • 良好的异常处理
  • 数据库curd的封装
  • 公共函数的调用

MVC大家都很熟悉了,话不多说,先来介绍下ppf框架的结构

ppf  ppf框架目录
├─Application                 应用目录
│  ├─Config                   配置文件目录
│  │  ├─Config.php            配置路径文件
│  │  └─Database.php          数据库配置文件
|
│  ├─module_name              模块目录
│  │  ├─Controller            控制器目录
│  │  ├─Model                 模型目录
│  │  └─View                  视图目录
|
│  ├─other_module_name        其他模块目录
│  │  ├─Controller            控制器目录
│  │  ├─Model                 模型目录
│  │  └─View                  视图目录
|
├─Cache                       缓存目录
|
├─Public                      css,js,image存放目录
│  ├─Css                
│  ├─Image              
│  └─Js                 
├─Library
│  ├─Common                   框架公用的函数方法存放目录
│  |  ├─Function.php          框架公用的函数方法
|     ├─ErrorCode.php         异常状态码类
|     ├─ErrorCodeCN.php       异常状态码对应中文解释文件
│  ├─Exception                框架异常处理模块
|     └─FrameException.php    模版异常处理类
|
│  ├─Sys                      框架系统核心
│     ├─Compile.php           模版引擎编译类
│     ├─Controller.php        控制器基类
│     ├─Db_Table_Abstract.php 数据库表抽象类
│     ├─Dispath.php           路由分发单例类
│     ├─Model.php             Model基类 
│     ├─Template.php          模版引擎基类
|     └─View.php              view视图基类
│
├─index.php                   入口文件
├─.htaccess                   用于apache的重写
├─README.MD                   就是本页面
├─.gitignore                  git可忽略的文件

准备

我们先建立这些目录跟文件,跟主流的php框架类似的结构,我们学习开发的成本就可以减小很多,大家可以很容易的进行学习跟模仿.我们先来实现以下MVC的功能

  • URI路由
  • 控制器
  • 模型
  • 视图

URI路由 (具体可以查阅ppf手册-URI路由)

在入口文件index.php中编写执行Dispath的路由单例的构造方法,然后在Dispath中实现路由分发以及各个MVC类的实例化

index.php

$path = str_replace(DIRECTORY_SEPARATOR,'/',dirname(__FILE__));
define("PPF_PATH",$path);
require_once(PPF_PATH.'/Application/Config/Config.php');
$allFile = scandir(PPF_PATH.'/Library/Sys/');
array_splice($allFile,0,2);//去掉前面的 '.' 和 '..'
//获取文件夹的所有文件
foreach($allFile as $key => $val)
{   
    if(pathinfo($val,PATHINFO_EXTENSION) == 'php')
    {   
        //加载Library/Sys下面的所有文件
       require_once(PPF_PATH.'/Library/Sys/'.$val);
    }   
}   
//初始化路由分发 根据request_uri来分发到各个控制器方法
$dispath = Dispath::init();

Dispath.php

$action_class_name();
    }
    public static function init()
    {
        //常用getInstance()方法进行实例化单例类,通过instanceof操作符可以检测到类是否已经被实例化
        if(!(self::$static_resource instanceof self))
        {
            self::$static_resource = new self();

        }
        return self::$static_resource;
    }
    private  function  __clone()
    {
        echo "该dispath类禁止被克隆";
    }
    // 自动加载控制器和模型类
    private static function loadClass($class)
    {
        $controllers = APPLICATION_PATH.'/'.Dispath::$current_module."/Controller/".$class.".php";
        $models = APPLICATION_PATH.'/'.Dispath::$current_module."/Model/".$class.".php";

        if (file_exists($controllers)) {
            // 加载应用控制器类
            include $controllers;
        } elseif (file_exists($models)) {
            //加载应用模型类
            include $models;
        } else {
            // 错误代码
        }
    }
}
?>


比较有意思的是,现在自动加载可以使用 spl_autoload_register(array($this, 'loadClass')); 这个函数,大家可以查阅这个函数的相关知识。比auto_load要先进的多

这里有3个静态变量Dispath::$current_module,$this::$current_controller,$this::$current_action,之后有很多机会使用他们

控制器 (具体可以查阅ppf手册-控制器)

仿造CI框架,我们构造了类似这样的控制器代码

load('Common/Function');
            $str = "hello_aaa.html";
            echo $str;
            $this->view->assign("result",$str);
            $this->view->show();
        }
    }
?>
  1. 我们先来实现上个步骤的路由能不能到这个控制器下

url: http://ppf.com/Index/Index/index

(这里的ppf.com是我的nginx vhost,大家可以尝试配置下nginx.conf来实现)

2.我们需要编写Controller的代码来实现

$this->load('Common/Function');
$this->view->assign("result",$str);

以及

$this->view->show();

功能

Controller.php

load('Exception/FrameException');
        $this->load('Common/ErrorCode');
        //设置异常处理函数
        restore_exception_handler();
        set_exception_handler(array($this,"setExceptionHandler"));

        $this->view = new View();
    }
    public function setExceptionHandler(Throwable $e = null) {
        $this->hasException = true;
        $this->ShowErrorHtml($e);

    }
    public function load($path) {
        if(is_array($path)) {
            foreach($path as $key => $val) {
                $this->load($val);
            }
        }else {
            require_once(PPF_PATH.'/Library/'.$path.".php");
        }
    }
}
?>

其中其他的先不考虑,主要看这一行

$this->view = new View();

这里Controller实现了View类的实例,我们就可以在View类中进行assign以及show方法的声明

模型 (具体可以查阅ppf手册-模型)

考虑到模型,也就是数据库交互这块,这个可以后面数据库封装的时候讲解,我们先来介绍模型的建立

1.先在IndexController.php中声明对模型的调用

public function addAction() {
            $indexModel = new IndexModel();
            $movie_list = $indexModel->test();
            $this->view->assign("movie_list",$movie_list);
            $this->view->show();
        }

这样的形式十分便于理解,这样可以指向Index模块下面Model目录下的IndexModel.php文件,并且实例化后调用test方法

indexModel.php

"test",
            'movie_pic' => '111.jpg',
            'movie_url' => 'www.baidu.com',
            'addtime'   => 'November 06,2017',
            'movie_says'=> '很好看很好看的电影,电影画面很炫丽的'
        );
        return $insertData;
    }
}
?>

这里我就简单的填写了数据返回,并没有调用数据库方法,这里继承了Model类,所以我们可以推测Model必定有数据库操作的方法

Model.php

db = $this::Db_init();
    }
}
?>

视图 (具体可以查阅ppf手册-视图)

视图的话先考虑框架的搭建,其他模版解析的部分可以再模版引擎章节实现


这里仅仅继承了Template类,而这个Template中就声明了2个方法assgin 以及show

 'Cache/',         //设置编译后存放的目录
        'need_compile' => true,           //是否需要重新编译 true 需要重新编译 false 直接加载缓存文件
        'suffix_cache' => '.htm',         //设置编译文件的后缀
        'cache_time' => 2000              //多长时间自动更新,单位秒  
    );

    /*
    *   构造函数实例化编译类Compile
    *
    */
    public function __CONSTRUCT() {
        $compile = new Compile();
        $this->compile = $compile;
    }

    public function set_config($key, $value) {
        if (array_key_exists($this->config)) {
            $this->config[$key] = $value;
        }
    }

    public function get_config($key) {
        if (array_key_exists($this->config)) {
            return $this->config[$key];
        }
    }

    /**
     *   缓存策略
     *   根据need_compile 是否需要重新编译
     *   以及php文件,model文件视图文件是否经过修改
     *   以及当前时间比该文件编译时间是否大于自动更新cache_time时间
     *   以上3点来决定是需要再次编译还是直接使用缓存文件
     *  @param  string $php_file php文件
     *  @param  array $model_file model文件群
     *  @param  string $html_file 视图文件
     *  @param  string $compile_file 编译文件
     *  @return bool   $default_status 是否需要重新编译
     */
    public function cache_strategy($php_file, $model_file, $html_file, $compile_file) {
        $default_status = false;
        foreach ($model_file as $key => $val) {
            if(file_exists($compile_file) && file_exists($val)) {
                if (filemtime($compile_file) < filemtime($val)) {
                    $default_status = true;
                    return $default_status;
                    die;
                    break;
                } else {
                    $default_status = false;
                }
            }
        }
        //echo filemtime($html_file) . "
" . filemtime($compile_file) ."
". time();die; if(file_exists($compile_file)) { $compile_file_time = filemtime($compile_file); } $time_minus = time() - $compile_file_time; if (($this->config['need_compile']) || ($time_minus > $this->config['cache_time']) || filemtime($compile_file) < filemtime($html_file) || filemtime($compile_file) < filemtime($php_file)) { $default_status = true; } else { $default_status = false; } //var_dump($default_status);die; return $default_status; } /** * 将变量赋值到$this->vaule中 * @param $key * @param $value */ public function assign($key, $value) { $this->value[$key] = $value; } /** * 视图跳转方法(包含了模版引擎,模版编译转化功能) * @param $file 视图跳转文件 * */ public function show($file = null) { /** * 将例如assign("test","aaa") 转化成 $test = 'aaa'; * 所以这块是有2个赋值情况 一个是$test = 'aaa' 另一个是 $this->value['test'] = 'aaa'; * 这里设定 可以支持多维数组传递赋值 * @param string $file 视图文件 */ foreach ($this->value as $key => $val) { $$key = $val; } $current_module = Dispath::$current_module; $current_controller = Dispath::$current_controller; $compile_file_path = PPF_PATH . '/' . $this->config['compiledir'] . $current_module . '/'; $php_file = APPLICATION_PATH . '/' . $current_module . '/Controller/' . $current_controller . 'Controller.php'; $model_file = array(); $model_file_path = APPLICATION_PATH . '/' . $current_module . '/Model/'; $allFile = scandir($model_file_path); array_splice($allFile, 0, 2);//去掉前面的 '.' 和 '..' //获取文件夹的所有文件 foreach ($allFile as $key => $val) { if (pathinfo($val, PATHINFO_EXTENSION) == 'php') { $model_file_arr[] = $model_file_path . $val; } } /** * 如果未指定视图名称则默认跳至该current_action的名称 * 在这块定义视图地址,编译php文件地址,缓存htm文件地址 */ if (!$file) { $current_action = Dispath::$current_action; $html_file = APPLICATION_PATH . '/' . $current_module . '/View/' . $current_controller . '/' . $current_action . '.html'; $compile_file = $compile_file_path . md5($current_controller . '_' . $current_action) . '.php'; $cache_file = $compile_file_path . md5($current_controller . '_' . $current_action) . $this->config['suffix_cache']; } else { $html_file = APPLICATION_PATH . '/' . $current_module . '/View/' . $current_controller . '/' . $file . '.html'; $compile_file = $compile_file_path . md5($current_controller . '_' . $file) . '.php'; $cache_file = $compile_file_path . md5($current_controller . '_' . $file) . $this->config['suffix_cache']; } /** * 如果存在视图文件html_file 则继续根据条件编译,否则跳至/Index/view/Notfound/index.html */ if (is_file($html_file)) { /** * 对compile_file_path进行是否为路径的判断 如果不是 则进行创建并赋予755的权限 */ if (!is_dir($compile_file_path)) { mkdir($compile_file_path); //chmod($compile_file_path, 0755); } /** * 这3行代码是将Controller.php文件某一方法例如:$this->assign("add",'test'); * 将这个以键值对的方式传给在__CONSTRUCT实例化的Compile类中,并通过compile方法进行翻译成php文件 * 最后ob_start()方法需要 include $compile_file; */ if ($this->cache_strategy($php_file, $model_file_arr, $html_file, $compile_file)) { $this->compile->value = $this->value; /** * @desc 这里是先编译include部分的内容,然后在全部编译完毕 */ ob_start(); $this->compile->match_include_file($html_file); $this->compile->compile($html_file, $compile_file); include "$compile_file"; /** * 这块是得到输出缓冲区的内容并将其写入缓存文件$cache_file中,同时将编译文件跟缓存文件进行赋予755权限 * 这时可以去看看Cache下面会有2个文件 一个是php文件 一个是htm文件 htm文件就是翻译成html语言的缓存文件 */ $message = ob_get_contents(); /** if(file_exists($compile_file)) { chmod($compile_file, 0777); } if(file_exists($cache_file)) { chmod($cache_file, 0777); } */ $file_line = file_put_contents($cache_file, $message); ob_end_flush(); } else { include "$cache_file"; } } else { include APPLICATION_PATH . '/Index/View/Notfound/index.html'; } } } ?>

这就能保证Controller中能够调用这2个方法,具体内容可以在模版引擎中讲解

到此为止,ppf简单的mvc功能应该能够work起来

大家可以下载ppf源码,进行研究,

源代码地址:https://github.com/taweisuode/ppf/

你可能感兴趣的:(完整实现PHP框架(1))