10月12日:
1. 写一个自定义异常类来处理上传过程以及各种错误
2. (选做) 写一个与指定数据表绑定的类, 实现基本的模型功能,例如查询, 新增, 更新,删除等操作
【1】自定义异常类来处理上传过程以及各种错误
有时候我们需要使用自定义的异常类,对特定类型的异常进行处理。
自定义异常类需要继承自Exception类,并添加自定义的成员属性和方法即可。接下来通过一个文件上传实例进行学习。
一般按如下格式使用自定义异常类:
/* 自定义的一个异常处理类,但必须是扩展内异常处理类的子类 */
class MyException extends Exception{
//重定义构造器使第一个参数 message 变为必须被指定的属性
public function __construct($message, $code=0){
//可以在这里定义一些自己的代码
parent::__construct($message, $code);
}
public function __toString() {
//重写父类方法,自定义字符串输出的样式
return __CLASS__.":[".$this->code."]:".$this->message."
";
}
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
代码实现如下:实例 --- 用自定义异常类 处理上传文件的错误信息
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 '文件上传成功';
} else {
throw new MyException('文件无法移动到指定目录, 请检查目录权限', 1008);
}
} else {
throw new MyException('非法操作', 1009);
}
} catch (MyException $e) { //捕获自定义的异常对象
echo $e->errorInfo(); //通过自定义的异常对象中的方法处理异常
}
?>
运行实例 »
点击 "运行实例" 按钮查看在线实例实例 --- 客户端 上传页面 代码
html>
文件上传#tg {
position: absolute;
left:0;
right:0;
top:0;
bottom:0;
margin: auto;
}
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);
}
运行实例 »
点击 "运行实例" 按钮查看在线实例
【2】写一个与指定数据表绑定的类, 实现基本的模型功能,例如查询, 新增, 更新,删除等操作
按照数据表的字段结构编写一个同名的类,在使用PDO对数据库操作时,可以把表中查询到的一条记录映射到类的成员属性上,从而可以用类的属性操作方法来处理表中数据。一条数据记录对应一个类对象,不再是通常的默认的数组元素。
代码实例如下:实例
// 类名与表名对应
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的数据映射模式内容来看看。