PHP学习笔记---实现导出csv功能(附带打包zip教程)

对于许多的从事数据智能开发的同僚来说,从库中提取出数据后进行数据整理并且导出csv文件的功能是很常见的,导出一个csv文件方便用其他的数据工具进行分析。所以在这里分享一下我在工作过程中实现导出csv文件功能的历程与所获。

前言

首先,我被告知需要在laravel框架中实现下载接口这个任务时,整个人是懵逼的,完全不知道怎么着手去实现这个功能,但是对于一个理工科生来说,碰到问题并不可怕,剥丝抽茧,一步一步来。我分析,既然要实现下载功能接口,首先需要做的就是提供一个接口,而如何做一个接口我在<<Laravel使用心得--简易路由操作>>中已介绍,向前端提供一个URI即达到了接口的意义,其次是如何实现下载,最后是如何写入一个csv文件,本篇文章就从后两个方向介绍,并且最后附带PHP中文件打包功能的实现介绍

本来想打包功能单独写一篇博客的,后来发现这个功能实现比较简单,而深层次的我也暂时不会,就附带本篇文章最后了

下载

一、通过传递HTTP报头实现下载

首先在度娘上找到的实现下载的方式之一:是通过向浏览器传递HTTP报头,告诉浏览器这个URI的相关动作让浏览器去实现。
HTTP报头是HTTP协议的一个部分,一般上用于客户端和服务端之间握手时的通信,通俗的理解就是 http服务器和客户端(一般为浏览器)之间数据传输之前的对话,告诉浏览器你想干什么。
而在PHP中实现HTTP报头参数传递功能的是header()方法,header() 函数向客户端发送原始的 HTTP 报头。其中认识到一点很重要,即必须在任何实际的输出被发送之前调用 header() 函数,例如在调用header()函数前不要写print_r()或var_dump()等函数。
传递报头参数的代码:

header("Content-type:text/csv");
header("Content-Disposition:attachment;filename=" . $start_date . '~' . $end_date . '_fare.csv');
header('Cache-Control:must-revalidate,post-check=0,pre-check=0');
header('Expires:0');
header('Pragma:public');

其中第一行是告诉浏览器我需要导出文件,格式是csv,在Content-type这个参数类型中可以指定许多的导出文本的格式,例如rar、zip这样的压缩包格式
传递这样的报头后,导出的文件的内容将是你在调用该header()函数的方法内的return值,例如return 123;则csv文件中就是123。
这种方式可以实现下载,但是总归看上去不太好看,如此优秀的laravel框架怎么可能会不涉及到下载方法的封装呢,于是后来使用了另一种方法。

二、通过response方法实现下载

在看了其他前辈写的代码中,我发现有一行代码

return reponse()->download($file)

看单词意思也知道这行代码是起什么作用的。Response是laravel框架中的门面(facade),在这个框架中是可以直接引用调用的功能
例如:

//响应重定向
Route::get('example/test24', function(){
    return Redirect::to('example/test21')->with('username', 'xiaoming');
});
//定制HTTP响应
Route::get('example/test21', function(){
    return Response::make('内容不存在', 404);
});
//响应视图
Route::get('example/test22', function(){
    return Response::view('test22');
});

以上的例子是response在封闭函数中的直接调用,在其他的地方自然也是可以直接使用的,而下载文件就可以使用Response::download()方法
我们先看一下这个的源代码:

    /**
     * Create a new file download response.
     *
     * @param  \\SplFileInfo|string  $file
     * @param  string  $name
     * @param  array  $headers
     * @param  string|null  $disposition
     * @return \\Symfony\\Component\\HttpFoundation\\BinaryFileResponse
     */
    public function download($file, $name = null, array $headers = [], $disposition = 'attachment');

可以看到这个下载方法的参数,有$file, $name = null, array $headers = [], $disposition = 'attachment',后面都有默认值,可以不传递,也可以用于参数扩展,利用这个方法就可以实现下载
例如:

public function getDownload()
{
    //PDF file is stored under project/public/download/info.pdf
    $file= public_path(). "/download/info.pdf";
    $headers = array(
              'Content-Type: application/pdf',
            );
    return Response::download($file, 'filename.pdf', $headers);
}

由此处的header()可以看出是要求下载一个pdf文件,而在laravel 5框架中使用此功能还可以使用

return response()->download($file, 'filename.pdf', $headers);

这种方式,功能是一样的,其中也可以只指定第一个参数,这样下载的文件就是你的文件之前指定好的类型。

写入csv文件方法

介绍了如何实现下载的两种方法,现在来说一下如何将数据写入csv文件

字符串连接方法

首先要知道,csv文件的内容其实就是一串拼接起来的字符串,起始指定好表头字符串,后面就以该表头的顺序依次拼接数据即可,只是在表头和每一行数据的末尾都添加一个换行符\\n来达到表格对齐的效果即可。
例如:

$head_str = "日期,姓名,年龄,学校\\n";
$cnt  = count($data);
for ($i =0;$i<$cnt;$i++) {
        $tmp = implode(",",$data[$i]);
        $head_str .= $tmp."\\n";
}

其中$data是从库中取出的数据的二维数组,而每一个第一层索引指向的就是对应的每一行数据,然后利用for循环遍历取出每一行数据进行拼接。
这一种方法是和传递HTTP报头实现下载的方法配合使用效果更好,因为在拼接完成后直接在方法内return $head_str,就能将整个数据内容读入到了下载的csv文件中。当然,也可以使用fwrite()方法写入一个新文件$file,然后利用response->download($file)方法下载该文件即可。

export()方法

后来发现,每次这样拼接数据非常的麻烦,可以写一个公共的方法,以便在其他地方实现类似的功能时可以直接调用

    public static function exportData($data = array(), $title = [])
    {
        $new_data = [];
        if (!empty($data)) {
            if(empty($title))
            {
                foreach ($data as $key => $val) {
                    $new_data[$key] = isset($val) ?   mb_convert_encoding($val, 'gbk', 'utf-8') : '';
                }
            } else {
                foreach ($title as $key => $val) {
                    $new_data[$key] = isset($data[$key]) ? mb_convert_encoding($data[$key], 'gbk', 'utf-8') : '';
                }
            }
            $str =  implode(',', $new_data);
            fwrite(self::$fp, $str."\\n");
        }
    }

这个方法的实现原理是将数据进行转码处理然后利用fwrite()方法写入一个新文件。其中self::$fp是指定的文件的路径,这个php手册上看一下fwrite()方法的介绍就能晓得参数的意思。写入了新的文件后就可以再通过reponse->download()方法来下载了。

php文件打包教程

在数据量非常庞大时,一次性取出大量的数据然后写入csv文件再下载的这个流程是不适用的,因为数据量庞大会导致取数时间很长,命令运行超出内存。此时可以采用的方法就是将大量的数据按时间维度写入多个csv文件,然后再根据需要的时间区间将多个csv文件打包下载即可,所以在这也讲一下我如何实现文件打包。
在php中,利用的是ZipArchive()类,通过这个类的实例化来实现打包。

依然是感兴趣的同学自行在php手册上学习

代码:

//获取zip包名
$zip_file = $save_path . '/' . $start_date . '-' . $end_date . '.zip';
if (file_exists($zip_file)) {
        return response()->download($zip_file);
}
//文件打包
$zip = new ZipArchive();
if ($zip -> open($zip_file, ZipArchive::CREATE) == true) {
        foreach ($file_dir as $file) {
             if (file_exists($file)) {
             $zip -> addFile($file, basename($file));
             }
        }
}
$zip -> close();

这样就实现了打包,其中$file_dir变量是你要打包的文件的路径数组,里面包含所有你想打包的文件路径,$zip_file变量是你想打包成zip文件的包的路径+名称。

有的同学在使用此方法时有时会不管用,以我的经验,一般都是文件的路径不正确,或者是没有指定绝对路径
注意:在$zip -> addFile()方法中不要使用路径变量拼接,最好在使用该方法前就写好路径。使用了拼接不会报错,但是依然会文件添加不进去,这里是一个大坑,我找了好久才发现。

最后:本人新手程序员,一起进步!!!

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 119,437评论 16 133
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 155,964评论 24 681
  • 亲爱的,每天都好想你,miss you so much 我发现,以前,我会生你的气好久,现在,上一秒生气下一秒就想...
    Meloy_D阅读 46评论 0 0
  • 姓名;沈军耀 公司;宁波大发化纤有限公司 组号;反省一组 期数;224期。日精进打卡一百零二十二天 (知~学习);...
    沈军耀阅读 400评论 0 2
  • 你们怎么找到自己的灵魂,并认定那就是灵魂的,我为什么找不到,也不清楚。
    二宁阅读 62评论 0 0