php基础教程首页投稿(暂停使用,暂停投稿)

完整实现PHP框架(1)

2017-06-27  本文已影响76人  jamespengge

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

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路由 (具体可以查阅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/

上一篇 下一篇

猜你喜欢

热点阅读