超简单的excel文件导出

希望以后还会用上吧。。。
推荐使用:phpspreadsheet 这个用了好多年了,个人蛮喜欢的。
具体选择看个人喜好吧,毕竟php导出excel的包/库的太多了。。。
最近写了个简单导出功能,当然也是简单的实现,应该满足日常项目使用。也可按需自行扩展或使用三方库,顺手记录下说不定啥时候又用上了。。
核心代码实现:

<?php


namespace App\Service\Excel;


class ExcelService
{

    /**
     * excel文本制表符
     */
    const EXCEL_TAB = "\t";

    const EXCEL_BOM = "\xEF\xBB\xBF";

    const EXCEL_EXT_DEFAULT = '.xls';

    /**
     * 表头
     * @var array
     */
    private array $header;

    /**
     * 列值
     * @var array
     */
    private array $columns;

    /**
     * 后缀
     * @var string
     */
    private string $ext;

    /**
     * 文件名称
     * @var string
     */
    private string $fileName;

    /**
     * 导出数据
     * @var array
     */
    private array $content;

    private \SplFileObject $fp;


    /**
     * @param $header | 表头
     * @param $columns | 对应字段
     * @param $title |  title
     * @param $ext |  文件后缀:csv,xlsx,xls
     */
    public function __construct($header, $columns, $title = '', $ext = '')
    {
        $this->header = $header;
        $this->columns = $columns;
        $this->ext = $ext ? $ext : self::EXCEL_EXT_DEFAULT;
        $this->fileName = $this->setFileName($title);

        $this->outputInit();
        $this->setBom();
        $this->setHeader();

    }

    /**
     * 初始响应文件
     */
    public function outputInit(): void
    {
        $output = 'php://output';
        header('Content-type:application/vnd.ms-excel; charset=utf-8');
        header("Content-Disposition: attachment; filename={$this->fileName}");
        $this->fp = new \SplFileObject($output, 'w');
    }


    /**
     * UTF-8格式解决中文乱码问题,写入BOM头
     * 也可采用iconv()转码gbk,excel默认gbk格式
     */
    public function setBom(): void
    {
        $this->fp->fwrite(self::EXCEL_BOM);
    }

    /**
     * 设置表格头部
     * @return void
     */
    public function setHeader(): void
    {
        $this->fp->fputcsv($this->header);
    }

    /**
     * 设置表格内容
     * @param $data
     * @return void
     */
    public function setContent($data): void
    {
        foreach ($data as $row) {
            $arr = [];
            foreach ($this->columns as $key => $column) {
                $text = $row[$column] ?? '';
                $arr[$key] = $text ? (self::EXCEL_TAB . $text) : $text;
            }
            $this->content[] = $arr;
        }
    }

    /**
     * 保存
     * @return void
     */
    public function save()
    {
        foreach ($this->content as $row) {
            $this->fp->fputcsv($row);
        }
        unset($this->fp);
    }

    /**
     * @param $title
     * @param $ext
     * @return string
     */
    public function setFileName($title): string
    {
        return $title . '-' . microtime(true) * 10000 . $this->ext;
    }

}

代码demo:

    public function export(Request $request)
    {
        /**
         * demo数据
         */
        $data = [
            ['id' => 1, 'name' => '张三', 'age' => 18],
            ['id' => 2, 'name' => '历史', 'age' => 20],
            ['id' => 3, 'name' => '王五', 'age' => 22],
            ['id' => 4, 'name' => '王五2', 'age' => 22],
            ['id' => 6, 'name' => 'ff', 'age' => 23],
        ];
        /**
         * 导出配置可写到config统一管理
         */
        $exportConfig = [
            ['id' => 'id', 'name' => '序号'],
            ['id' => 'name', 'name' => '姓名'],
            ['id' => 'age', 'name' => '年龄'],
        ];
        $header = array_column($exportConfig, 'name');
        $columns = array_column($exportConfig, 'id');
        $excel = new ExcelService($header, $columns, '导出试试', '.xlsx');
        $totalCount = count($data);
        $pageSize = 2;
        //大量数据分批次导出
        for ($i = 1; $i <= ceil($totalCount / $pageSize); $i++) {
            //获取数据
            $res = array_slice($data, ($i - 1) * $pageSize, $pageSize);
            $excel->setContent($res);
        }

        $excel->save();
    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 6

推荐使用xlswriter

1个月前 评论

你封装的超简单的导出类,有2个场景问题,你是怎么解决的?

  1. 一般数据导出,数据量有可能比较大,即便是分批取出数据,每次都需要setContent()函数,将数据存储,这时候你的内存开销会很大,你如何解决呢?
  2. 如果存在比较大的数据导出,为何不使用批次刷新缓冲方法,例如ob_flush和flush,或者说你对刷新缓冲怎么看,为什么要这么做?
1个月前 评论
working 1个月前

都4202年了 还在用xls... :smile:

1个月前 评论

直接xlswriter吧

1个月前 评论
随波逐流

分享一个自己在生产环境中用的 function.php,使用的是 xlswriter 扩展

定义 write_to_xlsx 函数

if (!function_exists('write_to_xlsx')) {

    function write_to_xlsx(string $filename, callable|array $callback, array $header = []): void
    {
        $excel = new Excel([
            'path' => '/tmp'
        ]);

        // 内存模式下, 关闭zip64
        $excel = $excel->constMemory(sprintf('%s.xlsx', Str::uuid()->toString()), null, false);
        if ($header) {
            $excel = $excel->header($header);
        }

        if (is_callable($callback)) {
            while ($data = $callback($excel)) {
                if ($data instanceof Collection) {
                    if ($data->isEmpty()) {
                        break;
                    }
                    $data = $data->toArray();
                }
                $excel = $excel->data($data);
            }
        } else {
            $excel = $excel->data($callback);
        }

        $outfile = $excel->output();

        // 检查文件夹
        if (!file_exists(dirname($filename))) {
            mkdir(dirname($filename), 0755, true);
        }

        // 移动文件
        rename($outfile, $filename);
    }
}

使用数组导出文件

$filename = '/tmp/test.xlsx';
$data = [
    ['1', '张三', '男', '25', '幸福路23-1号'],
    ['2', '李四', '男', '18', '幸福路23-2号'],
];
$header = ['ID', '姓名', '性别', '年龄', '住址'];
write_to_xlsx($filename, $data, $header);

使用闭包导出文件

$filename = '/tmp/test.xlsx';
$header = ['ID', '姓名', '性别', '年龄', '住址'];
$query = Person::query();
$page = 1;

write_to_xlsx($filename, function () use ($query, &$page) {
    $data = $query->forPage($page, 1000)->get();
    if ($data->isEmpty()) {
        return false;
    }
    $page++;
    return $data->transform(function (Person $person) {
       return [
           $person->id,
           $person->name,
           $person->sex,
           $person->age,
           $person->address,
       ];
    });
}, $header);
1个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!