PHP导出csv文件

简单说明

导出数据表格这个需求基本在所有互联网上的后台都会存在,你会很疑惑,为什么不导出excel而是csv,PHP就有一个导出excel的插件--PHPExcel啊,直接用多方便,下面我们来看一下excel的一下限制:

Excel2003及以下的版本一张表最大支持65536行,256列

Excel2007-2016版本一张表最大支持1048576行,16384列

而PHPExcel,在创建表之前,都是讲数据存储在变量中的,所以,当数据量到达一定的时候,就会报内存溢出

这个时候csv出现,csv以文本的格式导出,效率高,导出后可以用Excel的方式打开,简单方便

csv导出时需注意的点

1. 使用set_limit_time(0),设置导出的时候,程序执行时间不受限制(效果不大,因为Nginx也还会出现连接超时)

2. 分批读取数据,PHP运行的时候是将数据放在内存中的,数据量太大会导致内存溢出

导出时,查库分页数据不宜太多,每次分页执行完之后,需要unset数据集

在处理数据集输出时,不能再有数据库查询操作,否则,每一次循环就会产生n次数据库操作,占用大量内存空间--

--到达一定量只有,内存就会爆满,最终导致php线程异常退出,导出中断

3. csv导出可以使用putcsv函数或者直接echo对应的格式,但是这些操作并不会直接输出到浏览器,而是去到PHP的

输出缓存buffer里,要想把数据从缓存中读取到页面,则需要调用:

ob_flush(); //output_buffer

flush(); //buffer

这两个知识点,请自己去Google

4. 上代码:编写调用类

class Array2Csv
{

    public $_useGbk = true;
	private $fp = null;
	
	public function __construct(){
		//打开PHP文件句柄,php://output 表示直接输出到php缓存
		$this->fp = fopen('php://output', 'w');
	}
	
    //设置头部
    public function cvsHeader($filename)
    {
        //error_reporting(0);
        if($this->_useGbk) {
            header("Content-type:text/csv;charset=gbk");//application/vnd.ms-excel
        } else {
            header("Content-type:text/csv;charset=utf-8");
        }
        header("Content-Disposition:attachment;filename=" . $filename);
        header('Cache-Control:must-revalidate,post-check=0,pre-check=0,max-age=0');
        header('Expires:0');
        header('Pragma:public');
    }
	
	//采用putcsv封装格式
    public function outputData($data){
        foreach ($data as $key => $value) {
            //CSV的Excel支持GBK编码,一定要转换,否则乱码
            $data[$key] = mb_convert_encoding($value, 'GBK', 'UTF-8');
        }
        putcsv($this->fp,$data);
    }
	
    //刷新缓存,将PHP的输出缓存输出到浏览器上
    public function csvFlush(&$cnt){
        ob_flush();
        flush();
        $cnt = 0;
    }
	
	//关闭输出流
	public function closeFile(){
		fclose($this->fp);
	}
}

调用:

/**
 * 导出订单列表
 */
public function export()
{
	set_time_limit(0);
	$csv = new Array2Csv();
	$filename = '用户列表_' . date('YmdHis') . '.csv';
	// 输出Excel文件头,可把user.csv换成你要的文件名
	$csv->cvsHeader($filename);

	$head = [ '用户名称','用户ID', '电话号码', '邮箱','注册来源'];
	outputData($head);

	$limit = 10000;     //刷新缓存限制,避免php缓存过大
	$cnt   = 0;         //用于计算缓存数量
	$size  = 2000;
	while(1){
		$query = User::query()->orderBy('id', 'desc');
		$items = $query->take($size)->get();
		if(empty($items) || count($items) < 1){
			break;
		}
		foreach ($items as $key => $item) {
		//这里循环,不要再有查库操作或者其他请求操作,这样会占用大量PHP内存
			$data = [
				empty($item->username) ? '' : intval($item->username),
				empty($item->id) ? 0 : intval($item->id),
				$item->phone,
				$item->email
				$item->source
			];
			$cnt++;
			if ($limit == $cnt) {
				//刷新一下输出buffer,防止由于数据过多造成问题
				$csv->csvFlush($cnt);
			}
			outputData($data);
		}
		//释放内存
		unset($items,$paySource,$channels);
	}
	$csv->closeFile();  //每生成一个文件关闭
	exit;
}
5. 数据量超过104w怎么办?分割数据,然后再压缩,下面贴一个测试代码,具体可以自己去实现

//导出说明:因为EXCEL单表只能显示104W数据,同时使用PHPEXCEL容易因为数据量太大而导致占用内存过大,
//因此,数据的输出用csv文件的格式输出,但是csv文件用EXCEL软件读取同样会存在只能显示104W的情况,所以将数据分割保存在多个csv文件中,并且最后压缩成zip文件提供下载
function putCsv(array $head, $mark = 'zip_info', $fileName = "test.csv")
{
    set_time_limit(0);
    $sqlCount = 40;
    // 输出Excel文件头,可把user.csv换成你要的文件名
    /*header('Content-Type: application/vnd.ms-excel;charset=utf-8');
    header('Content-Disposition: attachment;filename="' . $fileName . '"');
    header('Cache-Control: max-age=0');*/

    $sqlLimit = 10;//每次只从数据库取100000条以防变量缓存太大
    // 每隔$limit行,刷新一下输出buffer,不要太大,也不要太小
    $limit = 10000;
    // buffer计数器
    $cnt = 0;
    //总数计数器
    $totalCount = 0;
    $fileNameArr = array();
    //转码
    foreach ($head as $key => $val){
        $head[$key] = mb_convert_encoding($val,'gbk','utf-8');
    }
    // 逐行取出数据,不浪费内存
    for ($i = 0; $i < ceil($sqlCount / $sqlLimit); $i++) {
        //$fp = fopen('php://output', 'w'); //生成临时文件
        $fp = fopen($mark . '_' .  $i . '.csv','w');
        //chmod($mark . '_' . $i . '.csv',777);//修改可执行权限
        $fileNameArr[] = $mark . '_' .  $i . '.csv';
        // 将数据通过fputcsv写到文件句柄
        fputcsv($fp, $head);
        $maxId = 1;
        $fileCount = 1;
        while (1){
            //每个文件生成的行数,这里是测试用的,具体根据自己的需求
            if($fileCount >= $sqlLimit || $totalCount >= 40){
                break;
            }
            //$dataArr = User::where('id','>',$maxId)->take(5000)->get()->toArray();
            $dataArr = $head;
            if(empty($dataArr) || count($dataArr) < 1){
                break;
            }
            foreach ($dataArr as $a) {
                $cnt++;
                $totalCount++;
                $fileCount++;
                $maxId = $a['id'];
                if ($limit == $cnt) {
                    //刷新一下输出buffer,防止由于数据过多造成问题
                    ob_flush();
                    flush();
                    $cnt = 0;
                }
                foreach ($a as $key => $val){
                    $a[$key] = mb_convert_encoding($val,'gbk','utf-8');
                }
                fputcsv($fp, $a);
            }
        }
        fclose($fp);  //每生成一个文件关闭
    }
    //进行多个文件压缩
    $zip = new \ZipArchive();
    $filename = $mark . ".zip";
    //$zip->open($filename, \ZipArchive::CREATE);   //打开压缩包
    if ($zip->open($filename, \ZipArchive::CREATE)!==TRUE) {
        exit('无法打开文件,或者文件创建失败');
    }
    foreach ($fileNameArr as $file) {
        //$zip->addFile($file, basename($file));   //向压缩包中添加文件
        if(file_exists($file)){
            $zip->addFile( $file, basename($file));//第二个参数是放在压缩包中的文件名称,如果文件可能会有重复,就需要注意一下
        }
    }
    $zip->close();  //关闭压缩包
    foreach ($fileNameArr as $file) {
        unlink($file); //删除csv临时文件
    }
    if(!file_exists($filename)){
        exit("无法找到文件"); //即使创建,仍有可能失败。。。。
    }
    //输出压缩文件提供下载
    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header('Content-disposition: attachment; filename=' . basename($filename)); // 文件名
    header("Content-Type: application/zip"); // zip格式的
    header("Content-Transfer-Encoding: binary"); //
    header('Content-Length: ' . filesize($filename)); //
    ob_clean(); //调用这个主要是为了清除压缩过程中产生的缓存,不调用时可能会导致压缩包解压失败
    @readfile($filename);//输出文件;
    //清空输出缓存
    ob_flush(); 
    flush();
    unlink($filename); //删除压缩包临时文件
}

$head = ['序号', '订单ID', '订单号', '名称', '用户名','用户ID', '电话号码', '邮箱', '实付(元)','优惠(元)', '支付方式', '状态', '创建时间','渠道号','支付来源','推荐人手机'];
putCsv($head);
完事,走人。。。


你可能感兴趣的:(PHP)