菜鸟常忽略的基础 - 了解文件上传

  • 作者:KK

  • 发表日期:2016.10.11


基本了解

Yii对于上传来的文件处理主要是用yii\web\UploadedFile这个类来做的

先保证服务器能成功上传文件并得到类似这样的$_FILES数组:

$_FILES = [
	//文件1
	'key1' => [
		'name' => '1.jpg',
		'type' => 'image/jpeg',
		'tmp_name' => 'C:\\Windows\\php110B.tmp',
		'error' => 0,
		'size' => 0,
	],
	
	//文件2
	'key2' => [
		//... 
	],
];

通常我们都只上传一个文件,所以就针对key1这个文件做上传文件处理讲解,首先要执行

$file = UploadedFile::getInstanceByName('key1');

将key1这个上传文件的信息转换成UploadedFile实例对象,此时你可以操作些什么?UploadedFile类参考文档在此

其中你需要注意这个UploadedFile实例的以下属性和方法,是我们经常在开发中需要用来实现文件验证和处理的:

tempName属性:浏览器发送文件到服务器后,服务器咋知道要放到你项目的哪里呢?所以暂时是放在服务器操作系统的临时目录了,这个属性就是它所处于临时目录的完整路径,要操作这个文件就需要这个路径

saveAs($保存路径)方法:这个方法用于将临时目录的文件保存到你指定的目录中

size属性:是一个数字,代表了文件的容量大小,单位是B,不是KB,所以size值1024才是1KB

type属性:文件的MimeType标识符,显示了文件的类型特征,不建议参考extension属性

extension属性:文件的后缀名,用于保存时保存一致的后缀名,看自己需要,有时候要统一将png、bmp什么的转换成jpg的话就没必要用原始的后缀名了

hasErrorerror属性:上传的文件可能是损坏的,或者是超出了服务器容量限制,要根据hasError报错给用户,以便用户知道自己上传的文件出了啥问题

name属性:就是$_FILES数组里面第一层的那个key了,也就是这个文件自己的key


手动验证+保存示例

通常我们要限制文件的上传大小,不能说人家999999MB的文件都给上传的对吧;然后我们还经常处理上传的图片,要限制宽高比,否则呈现出来会变形大家都不希望有这样的情况;还有文件类型

//假设在控制器
$file = UploadedFile::getInstanceByName('key1');
if($file->hasError){
	return '上传文件出错:' . $file->error;
}

if(!$file->size){
	return '文件内容是空的';
}

$allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp'];
if(!in_array($file->type, $allowTypes)){
	return '请上传图片';
}

if($file->size > 1024000){
	return '上传的图片不能大于1MB';
}

list($width, $height) = getimagesize($file->tempName);
if($width != $height){
	return '头像宽高比应为1:1,就是正方形图片';
}
//或
if($width / $height != 2){
	return '图片宽高比必须是2:1'; //更多图片比例校验自己写计算代码
}

//保存文件
$randomNumber = microtime() . mt_rand(111111, 999999);
$filename = md5($randomNumber) . '.' . $file->extension; //保持后缀
$randomFolder = substr($randomNumber, -2); //以最后两个数字作为保存的子目录,以免同一个目录存在太多文件造成索引压力或维护管理不方便
$savePath = '/upload/icon/' . $randomFolder . '/' . $filename;
if(!$file->saveAs(Yii::getAlias('@app/web') . $savePath)){
	throw new \yii\base\ErrorException('保存上传文件失败');
}

return Yii::$app->urlManager . $savePath; //返回路径地址给前端显示,你懂的

如果你想认真学习Yii框架,请手抄以上代码调试运行,以便深刻学习并认知到里面的东西,有时候光是copy代码其实学习的感受跟自己手打代码的感受深度是不同的哦!


正确姿势

上面的流程代码是手动处理全部校验的,其实你只要活用Yii框架就可以少写很多代码并且变得优雅,还可以让这套变得通用一点,让各处的文件上传都能共用大部分功能

办法就是利用表单模型,其实官网文档已经有教过

其原理就是先在表单模型里声明一个属性,这个属性用于保存UploadedFile的实例,可是底层并不是自动获取这个实例的,需要我们手动赋值,所以官网的代码里就有了这么一句:$model->imageFile = UploadedFile::getInstance($model, 'imageFile');

好了现在表单模型的imageFile是一个UploadedFile实例了,可以开始玩了,接下来需要结合表单模型rules()方法里的file校验规则,一般大概是这样写的:

use yii\web\UploadedFile;
use yii\helpers\FileHelper;

class ImageUploadForm extends \yii\base\Model{
	/**
	 * @var string 保存目录
	 */
	public $savePath = '';
	
	/**
	 * @var UploadedFile 上传文件对象实例
	 */
	public $file = '';

	//校验规则
	public function rules()
	{
		return [
			['imageFile', 'required'],
			['imageFile', 'file', //重点是这个file验证器,就是yii\validators\FilterValidator这个类
				'skipOnEmpty' => false, 
				'extensions' => 'png,jpg,gif,bmp', 
				'maxSize' => 1024000,
			],
			['imageFile', 'validateSize']
		];
	}

	//验证图片尺寸
	public function validateSize($param, $attrName){
		list($width, $height) = getimagesize($this->imageFile);
		if($width / $height != 2){
			$this->addError('size', '请上传宽高比为2:1的图片哦');
		}
	}
	
	public function upload(){
		if(!$this->validate()){
			return false;
		}
	
		$randomNumber = microtime() . mt_rand(111111, 999999);
		$filename = md5($randomNumber) . '.' . $this->file->extension; //保持后缀
		$randomFolder = substr($randomNumber, -2); //以最后两个数字作为保存的子目录,以免同一个目录存在太多文件造成索引压力或维护管理不方便
		
		$savePath = '/upload/icon/' . $randomFolder . '/' . $filename;
		$fullPath = Yii::getAlias('@app/web') . $savePath;
		if(!is_dir(dirname($fullPath))){
			FileHelper::createDirectory(dirname($fullPath));
		}
		
		if(!$this->file->saveAs($fullPath)){
			throw new \yii\base\ErrorException('保存上传文件失败');
		}
		
		$this->savePath = $savePath; //存给自己的属性是为了让外部获取这个保存后的路径到底是什么路径
		return true;
	}
}


//控制器:
$form = new ImageUploadForm([
	'imageFile' => UploadedFile::getInstanceByName('文件的key')
]);
if($form->upload()){
	return Yii::$app->urlManager->baseUrl . $form->savePath;
}else{
	throw new \yii\base\UserException($form->firstError[0]);
}