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));
else
return $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);
}
}
//自動解密ids
if(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框架就完成了~~
后面有什么要補的再補吧