2017-6-5
由于工作需要 打算自己实现一个简单的 MVC框架以完成工作需求
初步定义 框架需要完成的工作
1.单入口的路由功能
2.文件的自动载入
3.流水ID的加密以及自动解密
4.MVC文件夹模式
5.通用模板的引用
单入口的路由实现
项目接口的 public 目录中存在一个index.php 文件 作为 项目的唯一入口
文件功能主要是 项目全局变量的定义
define (\'ROOT_DIR\', realpath(dirname(dirname(dirname(__FILE__)))));
以及配置文件和全局方法的引入的引入
require_once LIB_DIR.DS.\'configs\'.DS.\'global.config.php\';require_once LIB_DIR.DS.\'configs\'.DS.\'function.config.php\';
为了使 入口文件简单 引入 路由文件
require_once APP_DIR.DS.\'router.php\';
以及
require_once APP_DIR . DS .\'autoload.php\';
router.php 文件
路由文件主要是通过分析 $_SERVER 数组进行 路由的转发 初步定义 项目路径请求的模式 /modules/controller/action
由于路由需要兼容几种模式
1. 单路径请求 /
2.全路径请求 /index/index/index
3.带参数get请求 /index/index/index?a=1
4.带参数的POST请求
5.重写路径请求 /index/index/index/a/1
需要分布分析 $_SERVER[\'REQUEST_URI\']
直接贴上代码
$_RequestUri = $_SERVER[\'REQUEST_URI\'];//print_r($_SERVER);$_DocumentPath = $_SERVER[\'DOCUMENT_ROOT\'];$_FilePath = __FILE__;$_AppPath = str_replace($_DocumentPath,\'\', $_FilePath);$urlPath = $_RequestUri;// 兼容 ? 号方式的参数$urlPathArr = explode(\'?\', $urlPath);$urlPath = $urlPathArr[0];// $wParams=array();// if(isset($urlPathArr[\'1\']))// {// $wParams=$urlPathArr[\'1\'];// }$wParamsArr = array_merge($_GET, $_POST);$appPathArr = explode(DS, $_AppPath);for($i =0; $i < count($appPathArr); $i++){$p = $appPathArr[$i];if($p){$urlPath = preg_replace(\'/^\/\'. $p .\'\//\',\'/\', $urlPath,1);}}$urlPath = preg_replace(\'/^\//\',\'\', $urlPath,1);$appPathArr = explode("/", $urlPath);$appPathArrCount = count($appPathArr);$arr_url = array(\'modules\'=>\'index\',\'controller\'=>\'index\',\'method\'=>\'index\',\'parms\'=> array(),);if(!empty($appPathArr[0])){$arr_url[\'modules\']= $appPathArr[0];}if(!empty($appPathArr[1])){$arr_url[\'controller\']= $appPathArr[1];}if(!empty($appPathArr[2])){$arr_url[\'method\']= $appPathArr[2];}$arr_url[\'parms\']= $wParamsArr;if($appPathArrCount >3){// 如果大于三 则说明后面是参数for($i =3; $i < $appPathArrCount; $i +=2){$arr_temp_hash = array(strtolower($appPathArr[$i])=> $appPathArr[$i +1]);$arr_url[\'parms\']= array_merge($arr_url[\'parms\'], $arr_temp_hash);}}//print_r($arr_url);// 转入 controller$module_name = $arr_url[\'modules\'];//$class_name = NAME_DIR.DIRECTORY_SEPARATOR.\'Controllers\'.DIRECTORY_SEPARATOR.ucfirst($module_name).DIRECTORY_SEPARATOR.ucfirst($arr_url[\'controller\']).\'Controller\';$class_name = NAME_DIR .\'\Controllers\\\'. ucfirst($module_name).\'\\\'. ucfirst($arr_url[\'controller\']).\'Controller\';$controller_name = $arr_url[\'controller\'];$controller_file = CONTROLLERS_DIR . DS . ucfirst($module_name). DS . ucfirst($controller_name).\'Controller.php\';//print_r($class_name);$method_name = $arr_url[\'method\'];if(file_exists($controller_file)){//print_r($controller_file);include $controller_file;//spl_autoload_register( array($class_name,$method_name) );//echo 32;die;$requestArr[\'path\']= $arr_url[\'modules\']. DS . $arr_url[\'controller\']. DS . $arr_url[\'method\'];$requestArr[\'server_name\']= $_SERVER[\'SERVER_NAME\'];$requestArr[\'method\']= $_SERVER[\'REQUEST_METHOD\'];$obj_module =new $class_name($arr_url[\'parms\'], $requestArr);if(!method_exists($obj_module, $method_name)){die("要调用的方法不存在");}else{//print_r($arr_url[\'parms\']);$obj_module->$method_name();}}else{die("定义的类不存在");}
当然以上的路由转入 离不开我们的 控制器的父类的支持
在父类的构造方法中,支持参数的传递
$this->params= $this->_addslashes($paramsArr);$this->autoDeCode();$this->request = $requestArr;// //判断是否登录$path ="/".strtolower($this->getPath());$this->mtoken=$this->session(\'mtoken\');if(empty($this->mtoken)&& substr($path,0,11)!=\'/user/login\'){$url =\'/user/login\';echo \'<script type="text/javascript">top.window.location.href="\'.$url.\'";</script>\';}
而我们的控制器 同样离不开文件的自动加载 不然 命名空间使用不了
autoload.php 同样 直接上代码
namespaceApplication;/*** 自动加载类* @author kinmos*/classAutoloader{// 应用的初始化目录,作为加载类文件的参考目录protectedstatic $_appInitPath =\'\';/*** 设置应用初始化目录* @param string $root_path* @return void*/publicstaticfunction setRootPath($root_path){self::$_appInitPath = $root_path;}/*** 根据命名空间加载文件* @param string $name* @return boolean*/publicstaticfunction loadByNamespace($name){// 相对路径$class_path = str_replace(\'\\\', DIRECTORY_SEPARATOR ,$name);// 先尝试在应用目录寻找文件if(self::$_appInitPath){$class_file =self::$_appInitPath . DIRECTORY_SEPARATOR . $class_path.\'.php\';}//print_r($class_file);// 文件不存在,则在上一层目录寻找if(empty($class_file)||!is_file($class_file)){$class_file = APP_DIR.DIRECTORY_SEPARATOR ."$class_path.php";}// 找到文件if(is_file($class_file)){// 加载require_once($class_file);//print_r($class_file);if(class_exists($name,false)){//echo $name;returntrue;}}returnfalse;}}// 设置类自动加载回调函数spl_autoload_register(\'\Application\Autoloader::loadByNamespace\');
这样一个完整的路由以及命名空间的自动加载功能都完成了。。
接下来 看下我 顶的目录结构
能自己写的东西 还是自己写的好~~ 目录结构 随意定的~
然后是数据层
数据层的话。嘿嘿。 这边框架没有直接连数据库 而是通过 API的方式 调用 workerman 的微服务进行数据的处理
这算是偷懒的,后面应该完善,微服务这个东西。。对于开发来说 还是稍微麻烦点的,如果以后简单的项目 就不需要 用了,直接连接数据库 简单暴力
2017-6-12 补充 数据层 链接数据库
在目录中添加一个db 的配置文件
<?php
namespace Config;
/**
* mysql配置
* @author
*/
class Db
{
//
public static $default = array(
\'host\' => \'192.168.0.88\',
\'port\' => 3306,
\'user\' => \'root\',
\'password\' => \'123456\',
\'dbname\' => \'kinwork\',
\'charset\' => \'utf8\',
);
}
配置着本地数据库的路径,另外 这个文件是可以扩展的。便于后面的分库以及分布式的部署
另外在Lib目录中添加了三个数据库操作文件
Db 文件,DbTable DbConnection
Db 是操作数据库的表层文件,DbTable 文件是实际进行数据库操作的类 DbConnection 是进行数据库链接的类 进行数据库连接池的 管理
然后在我们的Models 文件夹中添加了 Db文件夹 对应每个数据库表的一个文件
<?php namespace Http\Models\Db; use Lib\DbTable; /** * */ class User extends DbTable { protected $_name = \'user\'; protected $_primary = \'id\'; protected $_db = \'default\'; /* * 构造函数 */ public function __construct() { } }
其中 _name 是表名 _db 是对应的config的数据库配置 兼容数据库的分库应用
在实际使用的时候,
$DbTable_User = Db::DbTable(\'User\'); // 查询所有数据 $list=$DbTable_User->getList(\'1=1\'); print_r($list); // 查询 分页数据 $pagelist=$DbTable_User->pageList(\'1=1\',\'*\'); print_r($pagelist); // 查询单条数据 $row=$DbTable_User->getRow(array(\'id\'=>1)); print_r($row); // 添加数据 $id=$DbTable_User->insert(array(\'name\'=>\'kinmos\')); print_r($id); // 修改数据 $status=$DbTable_User->update(array(\'name\'=>\'kinmos\'),array(\'id\'=>1)); print_r($status); // 删除数据 $status=$DbTable_User->delete(array(\'id\'=>15)); print_r($status);
至此,数据层完成。
最后是视图 层
视图的直接用的php的视图,而没有去用 类似 smarty 的模板引擎 额,个人感觉 php自己就是一个不错的 模板引擎啊。。就不多引用别的东西了~~哈哈
视图的话,在我们的Views文件夹中,然后模板就有一个问题 就是模板的数据渲染
在父类 的ActionController.php 中封装了一个 view 方法 用来实现该功能
publicfunction getViewUrl(){$this->urlPath = $this->getPath();$Path_arr = explode("/",$this->urlPath);$Module = isset($Path_arr[0])&&$Path_arr[0]!=\'\'?$Path_arr[0]:"Index";$Controller = isset($Path_arr[1])&&$Path_arr[1]!=\'\'?$Path_arr[1]:"Index";$Method = isset($Path_arr[2])&&$Path_arr[2]!=\'\'?$Path_arr[2]:"index";return ucfirst($Module).\'/\'.ucfirst($Controller).\'/\'.$Method;}/** 跳到视图页面* $data = array() 数组格式 带到视图页面的参数*/publicfunction view($data = array()){$view = VIEWS_DIR . DS.$this->getViewUrl().\'.php\';extract($data, EXTR_OVERWRITE);ob_start();file_exists($view)?require $view :exit($view .\' 不存在\');$content = ob_get_contents();return $content;}
这样在我们的控制器中 当需要使用到 视图 就可以直接 $this->view($data); 就可以带出数据到视图模板中了
然后模板的通用引用 也就简单了
在我们的视图文件夹中 存在 一个Pub文件夹 存放所有的通用模板
<?php include_once(VIEWS_DIR .DS."Pub".DS."header.php");?>
至此,一个简单的 phpMVC框架就搭建好了~
然后秉承以前左大侠的教诲 系统安全性 是衡量一个系统好坏的重要因素
故而,这里简单做了些 安全性的控制
1.防注入
2.关键信息的加密
防注入 addslashes
publicfunction _addslashes($param_array){if(is_array($param_array)){foreach($param_array as $key=>$value){if(is_string($value))$param_array[$key]= addslashes(trim($value));}}else{$param_array = addslashes($param_array);}return $param_array;}
关键信息的加密的话,主要是针对 流水ID 自增字段
主要在 我们的公共方法 function.php 中封装了两个方法 用于加密和解密
这里的话,用到的对称加密方式, AES的对称加密 (关键信息被修改掉啦)
functionEncode($str){if($str ==\'\'){return\'\';}$aes =newAes();return urlencode($aes->encrypt($str));}functionDecode($str){if($str ==\'\'){return\'\';}$aes =newAes();if(strpos($str,\'%\'))return $aes->decrypt(urldecode($str));elsereturn $aes->decrypt($str);}
AES 方法类
classAes{private $_privateKey ="kinmoshelloword&*#";private $_byt = array(0x19,0x34,0x56,0x78,0x90,0xAB,0xCD,0xEF,0x12,0x34,0x56,0x78,0x90,0xAB,0xCD,0xEF);publicfunction toStr($bytes){$str =\'\';foreach($bytes as $ch){$str .= chr($ch);}return $str;}publicfunction encrypt($data){$vi = $this->toStr($this->_byt);$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->_privateKey, $data, MCRYPT_MODE_CBC, $vi);return base64_encode($encrypted);}publicfunction decrypt($data){$vi = $this->toStr($this->_byt);$encryptedData = base64_decode($data);$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->_privateKey, $encryptedData, MCRYPT_MODE_CBC, $vi);//print_r($decrypted);return rtrim($decrypted,"\0");}}
对于 Encode 方法 和Decode 方法的 url_decode 和url_encode 主要是为了防止 路径的转义导致的加解密不正确~
另外 主要是针对 流水ID的 所以 在我们的基类中添加了一个自动解密的方法
protectedfunction autoDeCode(){if(isset($this->params["id"])){$id=$this->params["id"];//print_r($id);die;//print_r($_GET);if(!empty($id)&&!preg_match("/^[0-9]+$/",$id)){$this->params["id"]=Decode($id);}}//自动解密idsif(isset($this->params["ids"])){$ids=$this->params["ids"];if(!empty($ids)){$newids="";//print_r($idsarr);if(is_array($ids))foreach($ids as $key => $value){if(!preg_match("/^[0-9]+$/",$value)){if($newids!="")$newids.=\',\'.Decode($value);else$newids=Decode($value);//$this->params["ids"]=}}else{if(!empty($ids)&&!preg_match("/^[0-9]+$/",$ids)){$newids=Decode($ids);}}$this->params["ids"]=$newids;}}}
这样的话,传到 我们的控制器中的参数 便会自动解密了~~ 方便
至此,一个简单的 MVC框架就完成了~~
后面有什么要补的再补吧