想要完整实现一个完整的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();
}
}
?>
- 我们先来实现上个步骤的路由能不能到这个控制器下
(这里的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源码,进行研究,
网友评论