zoukankan      html  css  js  c++  java
  • PHP 原生实现MVC框架

    2017-6-5
    由于工作需要 打算自己实现一个简单的  MVC框架以完成工作需求
     
    初步定义 框架需要完成的工作
    1.单入口的路由功能
    2.文件的自动载入
    3.流水ID的加密以及自动解密
    4.MVC文件夹模式
    5.通用模板的引用
     
     
    单入口的路由实现
     
    项目接口的 public 目录中存在一个index.php 文件 作为 项目的唯一入口
     
    文件功能主要是  项目全局变量的定义
    1. define ('ROOT_DIR', realpath(dirname(dirname(dirname(__FILE__)))));
    以及配置文件和全局方法的引入的引入
    1. require_once LIB_DIR.DS.'configs'.DS.'global.config.php';
    2. require_once LIB_DIR.DS.'configs'.DS.'function.config.php';
    为了使 入口文件简单  引入 路由文件
    1. require_once APP_DIR.DS.'router.php';
    以及
    1. 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']
     
    直接贴上代码
    1. $_RequestUri = $_SERVER['REQUEST_URI'];
    2. //print_r($_SERVER);
    3. $_DocumentPath = $_SERVER['DOCUMENT_ROOT'];
    4. $_FilePath = __FILE__;
    5. $_AppPath = str_replace($_DocumentPath,'', $_FilePath);
    6. $urlPath = $_RequestUri;
    7. // 兼容 ? 号方式的参数
    8. $urlPathArr = explode('?', $urlPath);
    9. $urlPath = $urlPathArr[0];
    10. // $wParams=array();
    11. // if(isset($urlPathArr['1']))
    12. // {
    13. // $wParams=$urlPathArr['1'];
    14. // }
    15. $wParamsArr = array_merge($_GET, $_POST);
    16. $appPathArr = explode(DS, $_AppPath);
    17. for($i =0; $i < count($appPathArr); $i++){
    18. $p = $appPathArr[$i];
    19. if($p){
    20. $urlPath = preg_replace('/^/'. $p .'//','/', $urlPath,1);
    21. }
    22. }
    23. $urlPath = preg_replace('/^//','', $urlPath,1);
    24. $appPathArr = explode("/", $urlPath);
    25. $appPathArrCount = count($appPathArr);
    26. $arr_url = array(
    27. 'modules'=>'index',
    28. 'controller'=>'index',
    29. 'method'=>'index',
    30. 'parms'=> array(),
    31. );
    32. if(!empty($appPathArr[0])){
    33. $arr_url['modules']= $appPathArr[0];
    34. }
    35. if(!empty($appPathArr[1])){
    36. $arr_url['controller']= $appPathArr[1];
    37. }
    38. if(!empty($appPathArr[2])){
    39. $arr_url['method']= $appPathArr[2];
    40. }
    41. $arr_url['parms']= $wParamsArr;
    42. if($appPathArrCount >3){
    43. // 如果大于三 则说明后面是参数
    44. for($i =3; $i < $appPathArrCount; $i +=2){
    45. $arr_temp_hash = array(strtolower($appPathArr[$i])=> $appPathArr[$i +1]);
    46. $arr_url['parms']= array_merge($arr_url['parms'], $arr_temp_hash);
    47. }
    48. }
    49. //print_r($arr_url);
    50. // 转入 controller
    51. $module_name = $arr_url['modules'];
    52. //$class_name = NAME_DIR.DIRECTORY_SEPARATOR.'Controllers'.DIRECTORY_SEPARATOR.ucfirst($module_name).DIRECTORY_SEPARATOR.ucfirst($arr_url['controller']).'Controller';
    53. $class_name = NAME_DIR .'Controllers\'. ucfirst($module_name).'\'. ucfirst($arr_url['controller']).'Controller';
    54. $controller_name = $arr_url['controller'];
    55. $controller_file = CONTROLLERS_DIR . DS . ucfirst($module_name). DS . ucfirst($controller_name).'Controller.php';
    56. //print_r($class_name);
    57. $method_name = $arr_url['method'];
    58. if(file_exists($controller_file)){
    59. //print_r($controller_file);
    60. include $controller_file;
    61. //spl_autoload_register( array($class_name,$method_name) );
    62. //echo 32;die;
    63. $requestArr['path']= $arr_url['modules']. DS . $arr_url['controller']. DS . $arr_url['method'];
    64. $requestArr['server_name']= $_SERVER['SERVER_NAME'];
    65. $requestArr['method']= $_SERVER['REQUEST_METHOD'];
    66. $obj_module =new $class_name($arr_url['parms'], $requestArr);
    67. if(!method_exists($obj_module, $method_name)){
    68. die("要调用的方法不存在");
    69. }else{
    70. //print_r($arr_url['parms']);
    71. $obj_module->$method_name();
    72. }
    73. }else{
    74. die("定义的类不存在");
    75. }
     
    当然以上的路由转入 离不开我们的 控制器的父类的支持
    在父类的构造方法中,支持参数的传递
    1. $this->params= $this->_addslashes($paramsArr);
    2. $this->autoDeCode();
    3. $this->request = $requestArr;
    4. // //判断是否登录
    5. $path ="/".strtolower($this->getPath());
    6. $this->mtoken=$this->session('mtoken');
    7. if(empty($this->mtoken)&& substr($path,0,11)!='/user/login'){
    8. $url ='/user/login';
    9. echo '<script type="text/javascript">top.window.location.href="'.$url.'";</script>';
    10. }
     
     
    而我们的控制器 同样离不开文件的自动加载  不然 命名空间使用不了
     
    autoload.php  同样  直接上代码
     
    1. namespaceApplication;
    2. /**
    3. * 自动加载类
    4. * @author kinmos
    5. */
    6. classAutoloader
    7. {
    8. // 应用的初始化目录,作为加载类文件的参考目录
    9. protectedstatic $_appInitPath ='';
    10. /**
    11. * 设置应用初始化目录
    12. * @param string $root_path
    13. * @return void
    14. */
    15. publicstaticfunction setRootPath($root_path)
    16. {
    17. self::$_appInitPath = $root_path;
    18. }
    19. /**
    20. * 根据命名空间加载文件
    21. * @param string $name
    22. * @return boolean
    23. */
    24. publicstaticfunction loadByNamespace($name)
    25. {
    26. // 相对路径
    27. $class_path = str_replace('\', DIRECTORY_SEPARATOR ,$name);
    28. // 先尝试在应用目录寻找文件
    29. if(self::$_appInitPath)
    30. {
    31. $class_file =self::$_appInitPath . DIRECTORY_SEPARATOR . $class_path.'.php';
    32. }
    33. //print_r($class_file);
    34. // 文件不存在,则在上一层目录寻找
    35. if(empty($class_file)||!is_file($class_file))
    36. {
    37. $class_file = APP_DIR.DIRECTORY_SEPARATOR ."$class_path.php";
    38. }
    39. // 找到文件
    40. if(is_file($class_file))
    41. {
    42. // 加载
    43. require_once($class_file);
    44. //print_r($class_file);
    45. if(class_exists($name,false))
    46. {
    47. //echo $name;
    48. returntrue;
    49. }
    50. }
    51. returnfalse;
    52. }
    53. }
    54. // 设置类自动加载回调函数
    55. spl_autoload_register('ApplicationAutoloader::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 HttpModelsDb;
    
    use LibDbTable;
    
    /**
     * 
     */
    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 方法 用来实现该功能
     
    1. publicfunction getViewUrl(){
    2. $this->urlPath = $this->getPath();
    3. $Path_arr = explode("/",$this->urlPath);
    4. $Module = isset($Path_arr[0])&&$Path_arr[0]!=''?$Path_arr[0]:"Index";
    5. $Controller = isset($Path_arr[1])&&$Path_arr[1]!=''?$Path_arr[1]:"Index";
    6. $Method = isset($Path_arr[2])&&$Path_arr[2]!=''?$Path_arr[2]:"index";
    7. return ucfirst($Module).'/'.ucfirst($Controller).'/'.$Method;
    8. }
    9. /*
    10. * 跳到视图页面
    11. * $data = array() 数组格式 带到视图页面的参数
    12. */
    13. publicfunction view($data = array()){
    14. $view = VIEWS_DIR . DS.$this->getViewUrl().'.php';
    15. extract($data, EXTR_OVERWRITE);
    16. ob_start();
    17. file_exists($view)?require $view :exit($view .' 不存在');
    18. $content = ob_get_contents();
    19. return $content;
    20. }
     
    这样在我们的控制器中 当需要使用到 视图 就可以直接 $this->view($data); 就可以带出数据到视图模板中了
     
    然后模板的通用引用  也就简单了
    在我们的视图文件夹中  存在 一个Pub文件夹 存放所有的通用模板
    1. <?php include_once(VIEWS_DIR .DS."Pub".DS."header.php");?>
     
      
    至此,一个简单的 phpMVC框架就搭建好了~
    然后秉承以前左大侠的教诲  系统安全性  是衡量一个系统好坏的重要因素
     
    故而,这里简单做了些 安全性的控制 
    1.防注入
    2.关键信息的加密
     
    防注入  addslashes
    1. publicfunction _addslashes($param_array){
    2. if(is_array($param_array)){
    3. foreach($param_array as $key=>$value){
    4. if(is_string($value))
    5. $param_array[$key]= addslashes(trim($value));
    6. }
    7. }else{
    8. $param_array = addslashes($param_array);
    9. }
    10. return $param_array;
    11. }
     
    关键信息的加密的话,主要是针对 流水ID  自增字段
     
    主要在 我们的公共方法  function.php 中封装了两个方法  用于加密和解密
    这里的话,用到的对称加密方式, AES的对称加密    (关键信息被修改掉啦)
    1. functionEncode($str)
    2. {
    3. if($str ==''){
    4. return'';
    5. }
    6. $aes =newAes();
    7. return urlencode($aes->encrypt($str));
    8. }
    9. functionDecode($str)
    10. {
    11. if($str ==''){
    12. return'';
    13. }
    14. $aes =newAes();
    15. if(strpos($str,'%'))
    16. return $aes->decrypt(urldecode($str));
    17. else
    18. return $aes->decrypt($str);
    19. }
     
    AES 方法类
    1. classAes
    2. {
    3. private $_privateKey ="kinmoshelloword&*#";
    4. private $_byt = array(0x19,0x34,0x56,0x78,0x90,0xAB,0xCD,0xEF,0x12,0x34,0x56,0x78,0x90,0xAB,0xCD,0xEF);
    5. publicfunction toStr($bytes)
    6. {
    7. $str ='';
    8. foreach($bytes as $ch){
    9. $str .= chr($ch);
    10. }
    11. return $str;
    12. }
    13. publicfunction encrypt($data)
    14. {
    15. $vi = $this->toStr($this->_byt);
    16. $encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->_privateKey, $data, MCRYPT_MODE_CBC, $vi);
    17. return base64_encode($encrypted);
    18. }
    19. publicfunction decrypt($data)
    20. {
    21. $vi = $this->toStr($this->_byt);
    22. $encryptedData = base64_decode($data);
    23. $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $this->_privateKey, $encryptedData, MCRYPT_MODE_CBC, $vi);
    24. //print_r($decrypted);
    25. return rtrim($decrypted,"");
    26. }
    27. }
     
    对于 Encode 方法 和Decode 方法的  url_decode  和url_encode 主要是为了防止 路径的转义导致的加解密不正确~ 
    另外 主要是针对 流水ID的  所以 在我们的基类中添加了一个自动解密的方法
     
    1. protectedfunction autoDeCode(){
    2. if(isset($this->params["id"]))
    3. {
    4. $id=$this->params["id"];
    5. //print_r($id);die;
    6. //print_r($_GET);
    7. if(!empty($id)&&!preg_match("/^[0-9]+$/",$id)){
    8. $this->params["id"]=Decode($id);
    9. }
    10. }
    11. //自动解密ids
    12. if(isset($this->params["ids"]))
    13. {
    14. $ids=$this->params["ids"];
    15. if(!empty($ids)){
    16. $newids="";
    17. //print_r($idsarr);
    18. if(is_array($ids))
    19. foreach($ids as $key => $value){
    20. if(!preg_match("/^[0-9]+$/",$value)){
    21. if($newids!="")
    22. $newids.=','.Decode($value);
    23. else
    24. $newids=Decode($value);
    25. //$this->params["ids"]=
    26. }
    27. }
    28. else
    29. {
    30. if(!empty($ids)&&!preg_match("/^[0-9]+$/",$ids)){
    31. $newids=Decode($ids);
    32. }
    33. }
    34. $this->params["ids"]=$newids;
    35. }
    36. }
    37. }
     
    这样的话,传到 我们的控制器中的参数 便会自动解密了~~ 方便
     
    至此,一个简单的 MVC框架就完成了~~
     
    后面有什么要补的再补吧
  • 相关阅读:
    Code Review
    关于calendar修改前的代码和修改后的代码
    程序员必备的代码审查(Code Review)清单
    一个数组中最大子数组的和并且加上测试用例
    阅读build to win的个人感想
    结对编码(柳祎、张许君)
    Review(patener)
    Review [myself]
    Impressions
    Array
  • 原文地址:https://www.cnblogs.com/kinmos/p/6945885.html
Copyright © 2011-2022 走看看