完整实现PHP框架(1)

作者: jamespengge | 来源:发表于2017-06-27 17:32 被阅读76次

    想要完整实现一个完整的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

    <?php
    /**
     *   路由分发单例类 Dispath
     *   该类是访问入口文件时实例化该类的
     *   构造函数用来指向当前url中的module controller action  并加载这些文件并实例化
     *   并且将当前的module,controller,action 写入静态变量中
     *
     */
    class Dispath
    {
        private static $ignore_module = true;
        private static $static_resource;
        public  static $current_module;
        public  static $current_controller;
        public  static $current_action;
        private final function __construct() {
            $url = $_SERVER["REQUEST_URI"];
            $module_controller_action = explode('/',$url);
            //这里加入默认忽略module方式
            if($this::$ignore_module == true && count($module_controller_action) == 3) {
                $module = 'Index';
                $controller = !empty($module_controller_action[1]) ? $module_controller_action[1] : 'Index';
                $action     = !empty($module_controller_action[2]) ? $module_controller_action[2] : 'index';
            }else {
                $module     = !empty($module_controller_action[1]) ? $module_controller_action[1] : 'Index';
                $controller = !empty($module_controller_action[2]) ? $module_controller_action[2] : 'Index';
                $action     = !empty($module_controller_action[3]) ? $module_controller_action[3] : 'index';
            }
            //对action进行过滤
            if(strpos($action,"?") !== false) {
                $actionArr = explode("?",$action);
                $action = $actionArr[0];
            }
            if(strstr($action,".html") || strstr($action,".php")) {
                $actionArr = explode(".",$action);
                $action = $actionArr[0];
            }
    
            $this::$current_module = $module;
            $this::$current_controller = $controller;
            $this::$current_action = $action;
    
            //增加自动加载类这个方式加载 controller,model
            spl_autoload_register(array($this, 'loadClass'));
            /*
            *  加载application 下面的所有Controller.php文件 并实例化这个类
            *  并访问其Action方法
            */
    
            $controller_class_name = $controller."Controller";
            if (!class_exists($controller_class_name)) {
                echo "请检查url中的控制器是否输入正确";die;
            }
            $current_controller = new $controller_class_name();
    
            $action_class_name = $action."Action";
            $current_controller->$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框架,我们构造了类似这样的控制器代码

    <?php
        class IndexController extends Controller {
            public function indexAction() {
                $this->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

    <?php
    /**
     *   控制器基类Controller
     *   所有的控制器都继承于他
     *   构造方法中需要实例化View类,并加载所有的该module下面的model.php文件
     */
    class Controller
    {
        private $loadException  = "FrameException";
        private $hasException   = false;
        protected $view;
        public function __CONSTRUCT()
        {
    
            //默认导入TestException异常类
            $this->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

    <?php
    error_reporting(E_ALL ^ E_NOTICE);
    class IndexModel extends Model
    {
        protected $_tablename = 'movielist';
        public function test() {
            $insertData = array(
                'movie_name'=>"test",
                'movie_pic' => '111.jpg',
                'movie_url' => 'www.baidu.com',
                'addtime'   => 'November 06,2017',
                'movie_says'=> '很好看很好看的电影,电影画面很炫丽的'
            );
            return $insertData;
        }
    }
    ?>
    
    

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

    Model.php

    <?php
    
    /**
     *  模型基类 Model
     *  主要是pdo_mysql的封装
     *
     */
    class Model extends Db_Table_Abstract
    {
        protected $table_name;
        private $strDsn;
        public $db;
        public function __CONSTRUCT($model_name = "") {
            include APPLICATION_PATH."/Config/Config.php";
            $this->db = $this::Db_init();
        }
    }
    ?>
    

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

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

    <?php
    /**
     *  视图类 继承于 Template类
     *
     */
    class View extends Template
    {
    }
    ?>
    

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

    <?php
    
    /**
     *  模版引擎基类Template 主要方法有以下
     *  对配置文件的设置
     *  对缓存策略的设计
     *  对assign 赋值的方法构造
     *  对show 方法的构造
     *  构造函数中加载编译类Compile
     */
    
    class Template
    {
        protected $compile;
        public $value = array();
        public $config = array(
            'compiledir' => '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) . "<br>" . filemtime($compile_file) ."<br>". 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)

        本文链接:https://www.haomeiwen.com/subject/gcmecxtx.html