博客列表 >MVC基础框架执行数据库CURD操作,PHP文件上传案例 - 第九期线上班 20191212

MVC基础框架执行数据库CURD操作,PHP文件上传案例 - 第九期线上班 20191212

MArtian
MArtian原创
2019年12月13日 15:20:171161浏览

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框架能搭建到大作业上面,希望完成大作业过程中能顺利一些吧,给自己加油!


声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议