10月12日:
1. 写一个自定义异常类来处理上传过程以及各种错误
2. (选做) 写一个与指定数据表绑定的类, 实现基本的模型功能,例如查询, 新增, 更新,删除等操作
【1】自定义异常类来处理上传过程以及各种错误
有时候我们需要使用自定义的异常类,对特定类型的异常进行处理。
自定义异常类需要继承自Exception类,并添加自定义的成员属性和方法即可。接下来通过一个文件上传实例进行学习。
一般按如下格式使用自定义异常类:
<?php /* 自定义的一个异常处理类,但必须是扩展内异常处理类的子类 */ class MyException extends Exception{ //重定义构造器使第一个参数 message 变为必须被指定的属性 public function __construct($message, $code=0){ //可以在这里定义一些自己的代码 parent::__construct($message, $code); } public function __toString() { //重写父类方法,自定义字符串输出的样式 return __CLASS__.":[".$this->code."]:".$this->message."<br>"; } public function errorInfo() { //为这个异常自定义一个处理方法 } } try { //使用自定义的异常类捕获一个异常,并处理异常 //主程序,如果发生异常,通过throw语句抛出 } catch (MyException $e) { //捕获自定义的异常对象 echo $e->errorInfo(); //通过自定义的异常对象中的方法处理异常 } ?>
文件上传过程说明:
通常一个文件以HTTP协议进行上传时,将以POST请求发送至Web服务器,Web服务器接收到请求并同意后,用户与Web服务器将建立连接,并传输数据。一般文件上传过程中将会经过如下几个检测步骤:
javascript校验;
服务端MIME类型检测
服务端目录路径检测
服务端文件扩展名检测
服务端文件内容检测
1、PHP使用超全局变量$_FILES来处理文件上传
$_FILES数组内容如下:
$_FILES['myFile']['name'] 客户端文件的原名称。
$_FILES['myFile']['type'] 文件的 MIME 类型,需要浏览器提供该信息的支持,例如"image/gif"。
$_FILES['myFile']['size'] 已上传文件的大小,单位为字节。
$_FILES['myFile']['tmp_name'] 文件被上传后在服务端储存的临时文件名,一般是系统默认。
$_FILES['myFile']['error'] 和该文件上传相关的错误代码。
UPLOAD_ERR_OK
值:0; 没有错误发生,文件上传成功。
UPLOAD_ERR_INI_SIZE
值:1; 上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值。
UPLOAD_ERR_FORM_SIZE
值:2; 上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值。
UPLOAD_ERR_PARTIAL
值:3; 文件只有部分被上传。
UPLOAD_ERR_NO_FILE
值:4; 没有文件被上传。
值:5; 上传文件大小为0.
2、文件被上传结束后,默认地被存储在了临时目录中,这时必须将它从临时目录中删除或移动到其它地方,如果没有,则会被删除。也就是不管是否上传成功,脚本执行完后临时目录里的文件肯定会被删除。
所以在删除之前要用PHP的 copy() 函数将它复制到其它位置,此时,才算完成了上传文件过程。
3、本例中使用自定义异常类给出上传过程中的结果 和 错误信息,在上传文件的客户端页面中设置一个隐藏的ifram,用来接收结果 或 错误信息。通过一个定时轮询ifram中body内容的变化,来决定是否显示iframe,从而实现页面无跳转的上传与信息提示。
最终的运行效果如下图:
catch(Exception $e)可以接受系统类exception和自定义的异常类。
catch(customException $e)只能接受throw抛出的自定义的异常类,不能接受系统类exception
代码实现如下:
实例 --- 用自定义异常类 处理上传文件的错误信息 <?php class MyException extends Exception { public function __construct($message, $code = 0) { parent::__construct($message, $code); } public function errorInfo() { include 'info.php'; //显示错误信息 } } try { //使用自定义的异常类捕获一个异常,并处理异常 // 配置上传参数 $fileType = ['jpg', 'jpeg', 'png', 'gif']; $fileSize = 3145728; $filePath = '\uploads\\'; $fileName = $_FILES['myfile']['name']; $tempFile = $_FILES['myfile']['tmp_name']; $uploadError = $_FILES['myfile']['error']; if ($uploadError != 0) { switch ($uploadError) { case 1:throw new MyException("上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值", 1001); case 2:throw new MyException('上传文档不允许超过3M', 1002); case 3:throw new MyException('上传文件不完整', 1003); case 4:throw new MyException('没有文件被上传', 1004); case 5:throw new MyException("文件大小为0", 1005); default:throw new MyException('未知错误', 1000); } } //通过扩展名判断文件类型 @$extension = end(explode('.', $fileName)); if (!in_array($extension, $fileType)) { throw new MyException('不允许上传' . $extension . ' 文件类型', 1006); } //为了防止同名覆盖, 将上传的文件重命名: md5+时间戳 $fileName = date('YmdHis', time()) . md5(mt_rand(1, 99)) . '.' . $extension; //传文件 $uploadPath = __DIR__ . $filePath; if (is_uploaded_file($tempFile)) { if (!file_exists($uploadPath) || !is_writable($uploadPath)) { throw new MyException('指定目录不存在且无法创建, 请检查目录', 1007); } if (move_uploaded_file($uploadPath . $fileName)) { echo '<strong><span style="color:red";>文件上传成功</span></strong>'; } else { throw new MyException('文件无法移动到指定目录, 请检查目录权限', 1008); } } else { throw new MyException('非法操作', 1009); } } catch (MyException $e) { //捕获自定义的异常对象 echo $e->errorInfo(); //通过自定义的异常对象中的方法处理异常 } ?> 运行实例 » 点击 "运行实例" 按钮查看在线实例
实例 --- 客户端 上传页面 代码 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <script src="jquery-3.4.1.min.js"></script> <title>文件上传</title> <style type="text/css" media="screen"> #tg { position: absolute; left:0; right:0; top:0; bottom:0; margin: auto; } </style> </head> <body> <form id="form1" action="upload.php" method="post" target="tg" enctype="multipart/form-data"> <br> <input type="hidden" name="MAX_FILE_SIZE" value="3145728"> <input type="file" name="myfile"><br><br> <input type="button" value="上传文件" onclick="upload()"> </form> <iframe name="tg" id="tg" frameborder="no" border="0" style="display:none" width="500px" > </iframe> <script type="text/javascript"> function upload() { $("#form1").submit(); var t = setInterval(function() { //获取iframe标签里body元素里的文字。即服务器响应过来的"上传成功"或"上传失败" var word = $("#tg").contents().find("body").text(); if (word != "") { $("#tg").show(); setTimeout(function(){$("#tg").hide();},1500); clearInterval(t); //清除定时器 } }, 1000); } </script> </body> </html> 运行实例 » 点击 "运行实例" 按钮查看在线实例
【2】写一个与指定数据表绑定的类, 实现基本的模型功能,例如查询, 新增, 更新,删除等操作
按照数据表的字段结构编写一个同名的类,在使用PDO对数据库操作时,可以把表中查询到的一条记录映射到类的成员属性上,从而可以用类的属性操作方法来处理表中数据。一条数据记录对应一个类对象,不再是通常的默认的数组元素。
代码实例如下:
实例 <?php // 类名与表名对应 class Staff { // 属性与表中的字段对应 private $staff_id; private $name; private $age; private $sex; private $position; private $hiredate; // 属性重载 public function __get($name) { return $this->$name; } public function __set($name, $value) { $this->$name = $value; } // 构造方法 public function __construct() { $this->hiredate = date('Y/m/d', $this->hiredate); $this->sex = $this->sex ? '男' : '女'; } } $pdo = new PDO('mysql:dbname=test', '*', '*'); $stmt = $pdo->prepare('SELECT * FROM `staff`'); $stmt->setFetchMode(PDO::FETCH_CLASS, Staff::class); $stmt->execute(); $staff = $stmt->fetchAll(); var_dump($staff[0]->name); ?> 运行实例 » 点击 "运行实例" 按钮查看在线实例
对于数据内容的查询显示,使用场景在数据库外部,采用上述映射到类的方法有助于数据的操作。而修改、新增和删除操作是把数据放入数据表中,目标场景是在数据库内部,采用映射到类的方式并没有提升操作优势,这些操作本就有专用的库类或函数模块来实现。
总结:
1、通过本次练习,了解了文件上传的过程与方法,了解了自定义异常类的使用方法;
2、文件上传安全漏洞不容忽视,实际项目中的检测和防范知识还需更进一步学习;
3、接本例练习了一种上传后页面不 发生跳转 并给出信息的实现方法;
4、数据表绑定类的关键操作是:setFetchMode(PDO::FETCH_CLASS, Staff::class);
5、练习中发现:映射类不需要显式实例化 就可以直接使用。上例中:$staff[0]->name;
6、在此基础上,可以扩展学习PHP的数据映射模式内容来看看。