PHP 导出CSV文件部分行错位

发布于 2022-09-06 20:52:14 字数 2410 浏览 18 评论 0

使用PHP fputcsv 或者 fwrite 方法导出大概1万7千条数据,出现大概6条到10条左右的数据错位情况。

代码:

public static function createCsv($data, $header = [], $filename = '')
{
    // 参数判断
    $data = is_object($data) ? $data->toArray() : $data;
    $header = is_array($header) ? $header : [];
    $filename = (1 > strlen(trim($filename))) ? 'csv-' : trim($filename);
    if (empty($data)) {
        return false;
    }

    // 文件名/目录
    $filename = $filename . date("YmdHis", time()) . rand(1000, 9999) . ".csv";
    $dir = \Env::get('runtime_path') . '/export/';
    if (!is_dir($dir) && !mkdir($dir)) {
        return false;
    }

    // 打开文件指针资源
    $handle = fopen($dir . $filename, 'w+');
    if (!$handle) {
        return false;
    }

    // 写入文件header头
    if (!empty($header)) {
        $res = fputcsv($handle, $header);
        if (!$res) {
            return false;
        }
    }

    // 判断header是索引数组还是关联数组
    $is_assoc = array_keys($header) !== range(0, count($header) - 1);

    // 写入文件内容
    $frequency = 0; // 频率
    $limit = 100000;
    foreach ($data as $datum) {
        $frequency++;
        if ($limit == $frequency) {
            // 刷新输出buffer
            ob_flush();
            flush();
            $frequency = 0;
        }
        // 如果是关联数组,则获取内容中和头部key相对应的值
        if ($is_assoc) {
            $csv = "";
            foreach(array_keys($header) as $item) {
                $val = str_replace('"', '""', "\t" . $datum[$item]);
                $csv .= '"' . $val . '",';
            }
            $csv = substr($csv, 0, -1);
            $csv .= "\n";
            $res = @fwrite($handle, $csv);
        } else {
            $res = fputcsv($handle, $datum);
        }

        if (!$res) {
            return false;
        }
    }

    // 关闭指针资源
    fclose($handle);
    return \Url::build('erp/Common/export', 'file=' . $filename);
}

出现的情况:

clipboard.png

同样的数据筛选之后发现也只有这一条出现问题

clipboard.png

源码:

clipboard.png

发现该行的逗号分隔符去到双引号里了,请问出现这种问题的原因是什么。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

成熟稳重的好男人 2022-09-13 20:52:14

经过一个下午的折腾,上周五还是发现出现该异常情况的原因。概括来说还是中文转编码的问题,即utf-8转gbk出现的小部分行缺少空格或者双引号的问题。
出现问题的原因:
在进行导出的过程中,接收其他程序传过来的数组参数,均为utf-8编码,我这边在调用fwrite或者fputcvs写入文件的过程中并没有进行 iconv("UTF-8", "GB2312//IGNORE", $header),而是将文件保存在了 export目录下,然后由vuejs做的后台调用位于同application下的一个共用类的 export方法,该方法是这样的:

public function export()
{
    $file = \Request::get('file');
    header('Content-Type: application/vnd.ms-excel');
    header('Content-Disposition: attachment;filename=export.csv');
    header('Cache-Control: max-age=0');
    $contents = file_get_contents(\Env::get('runtime_path') . '/export/' . $file);
    echo iconv("UTF-8", "GB2312//IGNORE", $contents);
    @unlink(RUNTIME_PATH . '/export/' . $filename);
    exit;
}

问题就出在 echo iconv("UTF-8", "GB2312//IGNORE", $contents); 这句代码上面,这里看到是对传入的整个文件进行转编码的,这里未进行测试到底多少行的数据会出现缺少逗号引号的问题。
最终的解决思路是,把共用类中对整个文件的转编码放到写入方法中,逐条转编码就解决了该问题。

更改后的代码:

public static function createCsv($data, $header = [], $filename = '')
{
    // 参数判断
    $data = is_object($data) ? $data->toArray() : $data;
    $header = is_array($header) ? $header : [];
    $filename = (1 > strlen(trim($filename))) ? 'csv-' : trim($filename);
    if (empty($data)) {
        return false;
    }

    // 文件名/目录
    $filename = $filename . date("YmdHis", time()) . rand(1000, 9999) . ".csv";
    $dir = Env::get('runtime_path') . '/export/';
    if (!is_dir($dir) && !mkdir($dir)) {
        return false;
    }

    // 打开文件指针资源
    $handle = fopen($dir . $filename, 'w+');
    if (!$handle) {
        return false;
    }

    // 写入文件header头
    if (!empty($header)) {
        foreach ($header as $key => $item) {
            $header[$key] = iconv("UTF-8", "GB2312//IGNORE", $item);
        }
        $res = fputcsv($handle, $header);
        if (!$res) {
            return false;
        }
    }

    // 判断header是索引数组还是关联数组
    $is_assoc = array_keys($header) !== range(0, count($header) - 1);

    // 写入文件内容
    $frequency = 0; // 频率
    $limit = 100000;
    foreach ($data as $datum) {
        $frequency++;
        if ($limit == $frequency) {
            // 刷新输出buffer
            ob_flush();
            flush();
            $frequency = 0;
        }
        // 如果是关联数组,则获取内容中和头部key相对应的值
        if ($is_assoc) {
            $csv = "";
            foreach(array_keys($header) as $item) {
                $datum[$item] = iconv("UTF-8", "GB2312//IGNORE", $item);
                $val = str_replace('"', '""', $datum[$item]); // 将单个双引号替换为两个双引号
                $csv .= '"' . $val . '",'; // 为每个字符增加双引号,并添加逗号分割符
            }
            $csv = substr($csv, 0, -1); // 去掉每行最后一个逗号
            $csv .= "\n"; // 添加换行符
            $res = @fwrite($handle, $csv);
        } else {
            $res = fputcsv($handle, $datum);
        }

        if (!$res) {
            return false;
        }
    }

    // 关闭指针资源
    fclose($handle);
    return Url::build('erp/Common/export', 'file=' . $filename);
}


public function export()
{
    $file = \Request::get('file');
    header('Content-Type: application/vnd.ms-excel');
    header('Content-Disposition: attachment;filename=export.csv');
    header('Cache-Control: max-age=0');
    $contents = file_get_contents(\Env::get('runtime_path') . '/export/' . $file);
    echo $contents;
    exit;
}

调用步骤:

先调用createCsv生成文件,再调用export下载。

———————————————————————分割线——————————————————————

另外一种解决思路:

csv文件直接存储 UTF-8 编码:
即不需要进行 iconv 转编码,这样会有个小问题就是MacOS中的Excel无法自动识utf-8编码,中文会出现乱码的情况。

原因:

Excel在读取csv的时候是通过读取文件头上的bom来识别编码的,如果文件头无bom信息,则默认按照unicode编码读取。(这个bom是微软自己定义的一种文件头部协定,顾名思义存储在文件头部,存储内容就是标识文件编码的信息。)而我们生成csv的平台不一定遵循微软的bom协议,导致如果输出非unicode编码的csv文件(例如utf-8),并且没有生成bom信息的话,Excel自动按照unicode编码读取,就会出现乱码问题了。

作者:李蛟 链接:https://www.zhihu.com/questio...
来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

根据知乎查到的答案,原来是这里未遵循微软的bom协议。原因找到了,解决方案就能出了。

解决方法:
fopen 方法下写入bom头,这里简单写一下步骤

function createCsv($filename, $header, $data) {
    $handle = fopen($filename, 'w+');
    // 添加BOM,标识为UTF-8格式
    fwrite($handle, chr(0xEF).chr(0xBB).chr(0xBF));
    // 写入头部
    fputcsv($handle, $header);
    // 逐行写入内容
    foreach ($data as $datum) {
        fputcsv($handle, $datum);
    }
    // 关闭指针资源
    fclose($handle);
}

另外再补充一下 Bom 简介

  • 在UCS 编码中有一个叫做”ZERO WIDTH NO-BREAKSPACE”的字符,它的编码是FEFF。
  • FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。
  • UCS规范建议我们在传输字节流前,先传输字符”ZERO WIDTH NO-BREAK SPACE”。
  • 这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;
  • 如果收到FFFE,就表明这个字节流是Little-Endian的。
  • 因此字符”ZERO WIDTH NO-BREAK SPACE”又被称作BOM。
  • UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。
  • 字符”ZERO WIDTH NO-BREAK SPACE”的UTF-8编码是EF BB BF。
  • 所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
  • Windows就是使用BOM来标记文本文件的编码方式的。

该内容来自 http://blog.csdn.net/chaozhi_... 这篇博客

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文