Blogger Information
Blog 40
fans 3
comment 0
visits 48220
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
MVC基础框架执行数据库CURD操作,PHP文件上传案例 - 第九期线上班 20191212
MArtian
Original
1159 people have browsed it

MVC框架实现CURD操作

思路

通过路由接收modelview参数,在控制器中分发请求,在编写过程中,发现原来通过一个control文件就可以实现所有页面的分发。 只需要写好底层的container服务容器与facade门面类,让绑定类与生成实例不需要在客户端执行,在control分发指令的时候向facade传递modelview参数,就可以实现从路由解析——>Control分发请求——>最后再通过门面类返回渲染结果。

那么门面类需要做的工作就非常复杂,在编写的时候,为了解决服务容器实例化的问题头疼好久,如果每次都需要在客户端将服务容器实例化,再去绑定类和实例化方法,那么这样的代码一定是低效的,并且耦合性很高,如果需要修改一个类的生成方法,那么所有页面都需要重写,所以我就在想能不能让"生成服务容器、绑定类、生成实例"在服务端完成。

后来我用门面类继承了服务容器作为子类,在facade门面类中将服务容器实例化,这样可以调用服务容器中的方法bindsmake方法,再在facade容器类中写两个静态方法来接收绑定与生成实例参数,分别传给服务容器的bindsmake方法,最后通过路由参数动态绑定类和生成实例。

这样客户端代码就非常的简单了,不需要在客户端执行绑定和生成,只需要写一个通用controller控制器,在控制器中向facade传递modelview参数,通过静态类来动态的生成modelview类与实例,这样避免了代码耦合,维护起来更方便。


生成路由参数部分

namespace common;
require __DIR__ . '/autoload.php';
define('_ROOT_','http://php.io/1206/'); //定义根目录
define('PAGES',['indexM','updateM','insertM','deleteM','selectM']); // 定义可以访问的页面
class Route{
    protected static $request = null;
    protected static $mvc = [];
    protected static $params = [];
    public static function getMVC(){
        static :: $request = explode( '/',$_SERVER['REQUEST_URI']);
        if( count(static :: $request) > 1){
//            判断数组长度是否大于2位,如果大于代表该路由含有MVC参数,并取出参数
//            截取MVC参数
            static :: $request =  array_slice(static :: $request, 2,3);
            list($control,$model,$view) = static :: $request;
            //给默认的index页面添加 M和V参数
            // 由于是用变量名生成的类名,所以会出现找不到类的命名空间问题,所以需要手动添加路径
//            这里做一个简单的判断,如果访问的是默认index.php页面,没有传递Model和View参数,自动添加并生成index页面
            if(empty($control)){
                $control = 'index.php';
            }
            if($control == 'index.php' && $model == '' && $view == ''){
                $model .= 'common\models\\'.substr($control,0,strrpos($control,'.')).'M';
                $view .= 'common\views\\'.substr($control,0,strrpos($control,'.')).'V';
            }else if(in_array($model,PAGES)){
//           否则判断传递的Model参数是否包含在可访问页面中,如果没有,返回错误信息
                $model = 'common\models\\'.$model;
                $view = 'common\views\\'.$view;
            }else{
                exit('您访问的是非法路径,请返回<a href="'._ROOT_.'index.php">首页</a>');
            }
            print_r($model,$view);
            print_r(in_array($model,PAGES));
            echo '<hr>';
            static :: $mvc = compact('control','model','view');
            return static :: $mvc;
        }
    }
    public static function getParams(){
        static :: $request = explode( '/',$_SERVER['REQUEST_URI']);
        if( count(static :: $request) > 6){
//            判断数组长度是否大于6位,如果大于代表该路由含有参数,并取出参数
//            截取参数
            static :: $request =  array_slice(static :: $request, 6);
//            重打包到关联数组中
            for($i = 0; $i<count(static::$request); $i+=2){
//                这里接收的参数,奇数是键名,偶数是值
                if(isset(static::$request[$i+1])){
//                $params的奇数值作为键名,$params的偶数值作为值,依次存储到$params参数
                    static :: $params[static::$request[$i]] = static :: $request[$i+1];
                }
            }
            static :: $request = compact('model','view','control');
//          将MVC参数传入关联数组中
            return static :: $params;
        }
    }
}

在路由部分做了请求分析,如果用户访问的路径不是系统支持路径,或者添加其他参数,则提示访问路径错误,并返回首页。
由于是动态生成的modelview参数,对modelview参数做了处理,在前面添加了命名空间路径,避免找不到类的问题。


容器与门面类部分

namespace common;
class Container{
//    实例数组
    protected $instances=[];
//    绑定方法,接收类名和构造参数
    public function binds($abstract,$concrete){
//      将类名存储到绑定中
        $this->instances[$abstract] = $concrete;
    }
//  将实例从容器中执行并返回,判断指向类是否已经绑定,如果绑定,生成该类实例,如果有构造参数,添加构造参数
    public function make($abstract,...$params){
        return call_user_func_array($this->instances[$abstract],$params);
    }
}
class Facade extends Container {
    protected static $container = null;
    protected static $data = [];
//    继承容器类,先在门面类中将容器类实例化
    public static function initialize(){
        static :: $container =  new Container();
    }
//    在门面类接收绑定参数,调用容器类中的binds方法,添加到容器类$instance属性中
    public static function bind($abstract, $concrete){
        static :: $container -> binds($abstract,$concrete);
    }
    public static function getData($model){
        static :: $data = static:: $container -> make($model) -> getData();
    }
    public static function fetchView($view){
        static :: $container -> make($view) -> fetchView(static::$data);
    }
    public static function header($titleName){
        static :: $container -> make('header',[$titleName]);
    }
    public static function footer(){
        static :: $container -> make('footer',[]);
    }
}

控制器部分

namespace common;
class Controller{
    protected $title = null;
    public function __construct($model,$view)
    {
        $this->title = substr($model,14);
        $this->title = substr($this->title,0,strrpos($this->title,'M'));
        switch($this->title){
            case 'index':
                $this->title = '首页';
                break;
            case 'update':
                $this->title = '大侠记录更新表';
                break;
            case 'delete':
                $this->title = '大侠记录删除表';
                break;
            case 'insert':
                $this->title = '大侠记录添加表';
                break;
            case 'select':
                $this->title = '大侠记录查询表';
                break;
            default:
                break;
        }
        Facade :: initialize();
        //执行绑定,添加头部,尾部,Model和View对象
        Facade :: bind('header',function($params=[]){return new header($params);});
        Facade :: bind('footer',function($params=[]){return new footer($params);});
        Facade :: bind($model,function() use($model){return new $model;});
        Facade :: bind($view,function() use($view){return new $view;});
        //渲染对象,将头部尾部,数据,实例化
        Facade :: header($this->title);  // 渲染头部,传递的参数是当前页面title标题
        Facade :: getData($model);  // 获取模型
        Facade :: fetchView($view); // 渲染页面
        Facade :: footer();       //渲染尾部
    }
}

在控制器中绑定了所有通用类,可以供所有页面调用,只需要将controller实例化,再传输modelview参数即可。


最后是客户端部分

namespace _MVC1;
use common\Route;
use common\Controller;
require __DIR__.'/common/autoload.php';
//获取路由
$route = Route::getMVC();
echo '<div style="width:500px;margin:0 auto;padding-top: 50px;"><pre>路由参数:'.print_r($route,true).'</pre></div>';
if($route == '非法路径'){
    die('您访问的是'.$route);
}else{
    //渲染页面
    $controller = new Controller($route['model'],$route['view']);
}

首先调用路由分析静态类,再判断路由返回参数是否为 "非法路径",如果不是,则向controller控制器传递路由中modelview参数,完成数据渲染。

至此,完成了一个控制器接收路由并分发modelview参数。


接下来是CURD部分:

首先是CURD封装类,该类支持简单的条件查询与排序和返回条目限制,并且为U和D操作添加了条件判断,如果用户输入条件为空,则不能执行该操作。

namespace common;
use common\Db;
require 'autoload.php';
class dbOperation{
    public $pdo;
    public $sql;
    public function __construct(){
        $this->pdo= Db::getInstance();
    }
    private function substrPos($str,$operator){  //取指定符号之前字符方法
        $str = substr($str,0,strrpos($str,$operator));
        return $str;
    }
    private function substriPos($str,$operator){  //取指定符号之后字符方法
        $str = substr($str,strripos($str,$operator)+1);
        return $str;
    }
    private function substrDel($str,$del){  //删除字符串末尾多余字符方法
        $str=substr($str,0,strlen($str)-$del);
        return $str;
    }
    private function getOperator($con){
        $operatorArr=['=','>','>=','<','<=','!='];  // 条件操作符集
        //拆分WHERE为 字段 和 值,为预处理做准备
        $whereParam=''; // WHERE的字段
        $whereValue=''; // WHERE的条件
        $operator=''; //返回操作符
        $existOperator=0; // 判断是否存可执行条件符号
        for($i=0;$i<count($operatorArr);$i++){
            if(strstr($con,$operatorArr[$i])){
                $whereParam=$this->substrPos($con,$operatorArr[$i]); // 获得拆分后的字段
                $whereValue=$this->substriPos($con,$operatorArr[$i]); // 获得拆分后的值
                $operator = $operatorArr[$i];   // 获取操作符
                $existOperator=1;  // 匹配到操作符,该条件成立
            }
        }
        if(!$existOperator){
            echo '暂时不支持该查询操作';
        }
        $where=$whereParam.$operator.':'.$whereParam;  //形参重写where条件
        $con = [
            $where,$whereParam,$whereValue
        ]; //将 形参条件,条件字段,条件值以数组形式返回
        return $con;
    }
    public function operation($operation,$fields,$values,$table,$where='',$order='',$limit='')  //查询语句
    {
        $time = time(); //设置日期格式
        if(empty($table) || empty($operation)){
            echo '请输入要操作的表';
            exit;
        }
        switch(strtoupper($operation)){
            case 'UPDATE':
                if(is_array($fields) && is_array($values)) {
                    if (count($fields) != count($values)) {
                        echo '字段和值不匹配';
                        exit;
                    }else if(empty($where)){
                        echo '该操作必须包含条件参数';
                        exit;
                    }else{
                        list($where,$whereParam,$whereValue) = $this->getOperator($where);  // 处理之后的WHERE语句,返回WHERE形参,WHERE字段,WHERE值
                    }
                }
                $updateParam = '`update`=:time'; //设置时间形参
                $this->sql=$operation.' `'.$table.'` SET ';
                foreach($fields as $field_v){
                    $this->sql.='`'.$field_v.'`=:'.$field_v.', ';  //设置形参,为预处理做准备 形参为:$field_v
                }
                $this->sql.=$updateParam;
                $this->sql.=' WHERE '.$where;
                ///设置查询条件
                $stmt=$this->pdo->prepare($this->sql);  //预处理SQL语句
                foreach($values as $v_k => $v_v){
                    $stmt->bindValue(":{$fields[$v_k]}",$v_v);  //循环绑定字段和值
                }
                $stmt->bindValue(':time',$time);  //绑定 TIME 更新时间
                $stmt->bindValue(":{$whereParam}",$whereValue);  //绑定 WHERE 条件
                $num=$stmt->execute();
                if($num > 0){
                    echo '<br>更新操作成功';
                }
                $this->sql=null; //释放SQL语句
                break;

            case 'DELETE':
                if(empty($where)){
                    echo '该操作必须包含条件参数';
                    exit;
                }else{
                    list($where,$whereParam,$whereValue) = $this->getOperator($where);  // 处理之后的WHERE语句,返回WHERE形参,WHERE字段,WHERE值
                }
                $this->sql=$operation.' FROM `'.$table.'` '.'WHERE '.$where;
                $stmt=$this->pdo->prepare($this->sql);
                $num=$stmt->execute([":{$whereParam}"=>$whereValue]);  //绑定WHERE条件
                if($num>0){
                    echo '<br>删除操作成功';
                }
                $this->sql=null; //释放SQL语句
                break;

            case 'INSERT':
                $insertField='';  // 处理添加字段
                $insertParam=''; // 设置添加值形参
                $dateField = '`date`'; //设置时间字段
                $dateParam = ':time'; //设置时间形参
                for($i=0;$i<count($fields);$i++){
                    $insertField.='`'.$fields[$i].'`,';
                    $insertParam.=':'.$fields[$i].',';
                }  //循环添加字段形参
                $insertField.=$dateField;
                $insertParam.=$dateParam;
                $this->sql=$operation.' INTO `'.$table.'` ('.$insertField.') VALUES ('.$insertParam.')';
                $stmt=$this->pdo->prepare($this->sql);
                foreach($values as $v_k => $v_v){
                    $stmt->bindValue(":{$fields[$v_k]}",$v_v);
                }
                $stmt->bindValue(":time",$time);  //绑定 日期 字段
                $num = $stmt->execute();
                if($num>0){
                    echo '<br>添加操作成功';
                }
                $this->sql=null; //释放SQL语句
                break;

            case 'SELECT' :
                $this->sql='SELECT ';
                if($fields[0] != '*'){  // 判断是否查询所有字段
                    foreach($fields as $field){
                        $this->sql.='`'.$field.'`,';
                    }
                    $this->sql=$this->substrDel($this->sql,1);  //删除末尾多余','
                }//添加查询字段
                else {
                    $this->sql.='*';
                }
                $this->sql.=' FROM `'.$table.'` ';
                if(!empty($where))
                {
                    list($where,$whereParam,$whereValue) = $this->getOperator($where);
                }
                empty($where) ?: $this->sql.='WHERE '.$where;
                empty($order) ?: $this->sql.=' ORDER BY '.$order;
                empty($limit) ?: $this->sql.=' LIMIT '.$limit;
                $stmt=$this->pdo->prepare($this->sql);
//                echo $this->sql.'<br>';
                $stmt->bindValue($whereParam,$whereValue);//绑定WHERE条件
                $stmt->execute();
//                echo '<br><br>预处理语句生成<br><br>';
//                $stmt->debugDumpParams();  //预处理语句调试
//                echo '<br>';
//                print_r($stmt->errorInfo());  // 错误信息捕捉
                $stmt->setFetchMode(\PDO::FETCH_ASSOC);
                $result=$stmt->fetchAll();
                echo '<table><thead><tr>
                <th>大侠姓名</th>
                <th>来自何处</th>
                <th>所习武功</th>
                <th>心法等级</th>
                </tr></thead>';
                foreach($result as $row_v){
                    echo '<tr><td>' . $row_v['name'] . '</td>
                  <td>' . $row_v['from'] . '</td>
                  <td>' . $row_v['skill'] . '</td>
                  <td>' . $row_v['level'] . '</td></tr>';
                }
                echo '</table>';
                echo '<span>总计'.$stmt->rowCount().'人</span>';
                $this->sql=null; //释放SQL语句
                break;
            default:
                break;
        }
    }

    public function __destruct(){
        $this->pdo=null;
        echo '<p>连接已断开</p>';
    }
}

下面是封装类的操作语句示例:

$db->operation('UPDATE',array('name','from','skill','level'),array('乔峰','契丹','降龙十八掌','5'),'wuxia','id=14');
$db->operation('INSERT',array('name','from','skill','level'),array('虚竹','逍遥派','小无相功','8'),'wuxia');
$db->operation('SELECT',array('name','from','skill','level'),'','wuxia','level>7','level ASC','10');
$db->operation('DELETE','','','wuxia','id=13');

需要向该类传递要执行的操作,以及字段,表名,条件等。


下面是表单的action页面

namespace action;
use common\dbOperation;
require '../common/autoload.php';
$form = $_POST;
$db = new dbOperation();
echo '<!doctype html>
<html lang="zh-CN">
<head> 
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="'. \common\Root::$url.'css/style.css">
    <title>返回结果</title>
    <style>
        p{
            padding: 10px 20px;
            background: #e8e8e8;
            line-height: 1.5;
            width: 30%;
            margin: 20px auto;
        }
    </style>
</head>
<body><main>';
if($db->pdo){
    echo '<p>连接成功</p>';
}
//echo '<pre>'.print_r($form,true).'</pre>';
switch($form['op']){
    case 'insert' :
    $db->operation('INSERT',array('name','from','skill','level'),array($form['name'],$form['from'],$form['skill'],$form['level']),'wuxia');
    break;
    case 'update' :
    $db->operation('UPDATE',array('name','from','skill','level'),array($form['name'],$form['from'],$form['skill'],$form['level']),'wuxia','id='.$form['id']);
    break;
    case 'select' :
    $db->operation('SELECT',array('name','from','skill','level'),'','wuxia',$form['id'],$form['order'],$form['limit']);
    break;
    case 'delete' :
    $db->operation('DELETE','','','wuxia','id='.$form['id']);
    break;
}
echo '<a href="javascript:window.history.back(-1)">返回</a></main></body></html>';

在action页面中已经写好了4个语句的模版,通过用户传递过来的值来分析要执行的CURD操作,再把用户传递的值分别添加到每个语句中。

TIM截图20191212154244.png

每个操作对应一个modelview文件。

TIM截图20191212161042.png

整体文件结构。


下面是演示

基本路由分发

route.gif

添加操作

insert.gif

更新操作

update.gif

删除操作

delete.gif

查询操作

select.gif


PHP文件上传

PHP文件上传需要注意以下几点:

  1. 扩展名设定

  2. 文件扩展名判断

  3. 文件大小设定

  4. 文件要重命名,避免重名冲突

  5. 设置接收上传文件的文件夹路径

PHP的超全局变量$_FILES内置数组结构,本身是二维数组,可以参照以下结构自定义变量,拆分$_FILES参数。

Array (     [file] => Array     (         [name] =>             [type] =>             [tmp_name] =>             [error] => 4             [size] => 0         ) )
$files = $_FILES['file']; if(empty($files['name'])){ //如果$_FILES内的name键为空,则代表没有文件上传     echo '<script>alert("未上传文件");location.assign("index.html")</script>'; } $fileType = ['jpg','png','gif'];  //设置可接受文件类型 $fileSize = $_POST['MAX_FILE_SIZE'];  //隐藏域中设置的文件大小 $filePath = '/uploads/';  // 文件上传目录 $fileName = $files['name']; // 上传原始的文件名 $tempFile = $files['tmp_name']; //服务器上的临时文件名 $uploadError = $files['error']; // 返回错误代码,大于0代表有错误 if($uploadError>0){     switch ($uploadError) {         case 1:         case 2: die('上传文件不允许超过3M');         case 3: die('上传文件不完整');         case 4: die('没有文件被上传');         default: die('未知错误');     } } //判断文件扩展名是否正确 $findExtension = substr($fileName,strrpos($fileName,'.') + 1); //查找文件名中的'.'最后一次出现的位置,用substr函数切割字符串,起始位置为最后一次'.'出现的位置,结束位置为最后一位 if(!in_array($findExtension,$fileType)){ //检测获取到的文件名是否在支持的类型中     die ('不支持该类型文件上传'); // } //为了防止文件名重复,采用当前时间戳转换日期格式+md5加密随机数 echo '<hr>'; $fileName = date('YmdHis',time()) . md5(mt_rand(1,99)) . '.' . $findExtension; echo $fileName; //判断文件是否上传成功,首先判断是否是通过Post上传 if(is_uploaded_file($tempFile)){     if(move_uploaded_file($tempFile, __DIR__ . $filePath . $fileName))     {// 提示用户上成功,并返回上一个页面,再强行刷新当前页面         echo '<script>alert("上传成功");history.back();</script>';     }else {         die('文件无法移动到指定目录,请检查目录权限');     } }else{     die('非法操作'); } exit();

总结

这几天一直在纠结MVC框架,几乎每天所有精力全部放在这上面了,在课堂上学习的东西一旦去实践的时候,脱离了课堂案例,感觉无从下手。
自己写代码也纠结,如果只是为了实现功能,那么这个作业也不难,但是在编写过程中,自己明明知道这样写是不通用的、低效的,就一直在就纠结怎么改,怎么让代码更优雅、更高效、更便于维护,这是一个程序员的基本思维。
写代码三句一报错,这几天每一天都在与Fatal Error, Warning斗争!有的时候实在是找不到错误,不知道怎么改,就想实在写不出来的时候就想要不糊弄一下算了,就用低效的方法做好了,但是低效的代码实在是写不下去,可能这就是我纠结的点吧,知道自己可以做的更好,所以不能将就,怎么也要把错误解决。

在课堂上学习的东西,只有在实践的时候才会明白这些代码为什么这样写,才会明白老师为什么要这样教,也深深感觉到自己知识浅薄。这个MVC框架虽小,但是一个人要完成从"逻辑 ——命名空间——代码层次——文件层级管理",从数据层到表现层,全部都要自己去做,脑子里要对这些东西在构造层面去思考,再到具体实现,感觉到程序员真不是一个容易的工作,给用户呈现的只是一个简单的页面,背后却做了非常多的努力和工作。

这几天为了做这个框架耽误了好多进度,同学们都已经在做大作业了,希望我的这个小MVC框架能搭建到大作业上面,希望完成大作业过程中能顺利一些吧,给自己加油!


Correcting teacher:天蓬老师天蓬老师

Correction status:qualified

Teacher's comments:基本思路应该是先用传统方式将基本功能实现, 再考虑用依赖注入传递对象到其它类中, 最后再将要用到的对象的创建过程, 用容器可行封装, 一步步来就会非常的清楚, 且容易实现, 一旦出错, 很快能找到bug, 并且可以快速退回去
Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post