PHP 导出导入工具封装

wen PHP项目 2

本文目录导读:

PHP 导出导入工具封装

  1. 基础导出导入类
  2. 服务类封装(业务逻辑层)
  3. 实用示例
  4. Composer依赖
  5. 配置文件示例

我来为您设计一个完整的PHP导入导出工具封装,支持Excel、CSV格式,并提供灵活的配置选项。

基础导出导入类

<?php
/**
 * 数据导出导入工具类
 * 支持Excel(需要PHPExcel/PHPOffice)、CSV格式
 */
class DataExportImport {
    private $config;
    private $errors = [];
    /**
     * 构造函数
     * @param array $config 配置选项
     */
    public function __construct($config = []) {
        $this->config = array_merge([
            'export_path' => __DIR__ . '/exports/',
            'import_path' => __DIR__ . '/imports/',
            'max_file_size' => 10485760, // 10MB
            'allowed_extensions' => ['xls', 'xlsx', 'csv'],
            'chunk_size' => 1000, // 分批处理数量
            'time_limit' => 300, // 最大执行时间(秒)
        ], $config);
        // 设置PHP执行时间
        if ($this->config['time_limit']) {
            set_time_limit($this->config['time_limit']);
        }
        // 创建目录
        $this->createDirectories();
    }
    /**
     * 创建必要的目录
     */
    private function createDirectories() {
        foreach (['export_path', 'import_path'] as $path) {
            if (!is_dir($this->config[$path])) {
                mkdir($this->config[$path], 0755, true);
            }
        }
    }
    /**
     * 导出数据到Excel
     * @param array $headers 表头
     * @param array $data 数据
     * @param string $filename 文件名
     * @return string|false 文件路径或false
     */
    public function exportToExcel($headers, $data, $filename = '') {
        try {
            // 检查PHPExcel类是否存在
            if (!class_exists('PHPExcel') && !class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet')) {
                throw new Exception('请安装 PHPExcel 或 PhpOffice\PhpSpreadsheet 库');
            }
            $filename = $filename ?: date('YmdHis') . '_export';
            $filename = $this->checkFilename($filename);
            if (class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet')) {
                // 使用 PhpSpreadsheet
                $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
                $sheet = $spreadsheet->getActiveSheet();
                // 写入表头
                foreach ($headers as $key => $value) {
                    $sheet->setCellValueByColumnAndRow($key + 1, 1, $value);
                }
                // 写入数据
                $rowNum = 2;
                foreach ($data as $row) {
                    foreach ($row as $colNum => $value) {
                        $sheet->setCellValueByColumnAndRow($colNum + 1, $rowNum, $value);
                    }
                    $rowNum++;
                }
                // 保存文件
                $writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
                $filePath = $this->config['export_path'] . $filename . '.xlsx';
                $writer->save($filePath);
                // 自动下载
                if (php_sapi_name() !== 'cli') {
                    $this->downloadFile($filePath, true);
                }
                return $filePath;
            } else {
                // 使用 PHPExcel
                $this->exportWithPHPExcel($headers, $data, $filename);
            }
        } catch (Exception $e) {
            $this->errors[] = '导出失败: ' . $e->getMessage();
            return false;
        }
    }
    /**
     * 导出数据到CSV
     * @param array $headers 表头
     * @param array $data 数据
     * @param string $filename 文件名
     * @param string $delimiter 分隔符
     * @return string|false 文件路径或false
     */
    public function exportToCSV($headers, $data, $filename = '', $delimiter = ',') {
        try {
            $filename = $filename ?: date('YmdHis') . '_export';
            $filename = $this->checkFilename($filename);
            $filePath = $this->config['export_path'] . $filename . '.csv';
            // 打开文件流
            $file = fopen($filePath, 'w');
            // 添加BOM,防止中文乱码
            fwrite($file, "\xEF\xBB\xBF");
            // 写入表头
            if (!empty($headers)) {
                fputcsv($file, $headers, $delimiter);
            }
            // 写入数据
            foreach ($data as $row) {
                fputcsv($file, $row, $delimiter);
            }
            fclose($file);
            // 自动下载
            if (php_sapi_name() !== 'cli') {
                $this->downloadFile($filePath, true);
            }
            return $filePath;
        } catch (Exception $e) {
            $this->errors[] = '导出失败: ' . $e->getMessage();
            return false;
        }
    }
    /**
     * 使用PHPExcel导出
     * @param array $headers 
     * @param array $data 
     * @param string $filename 
     */
    private function exportWithPHPExcel($headers, $data, $filename) {
        $objPHPExcel = new PHPExcel();
        $sheet = $objPHPExcel->getActiveSheet();
        // 设置表头
        foreach ($headers as $key => $value) {
            $sheet->setCellValueByColumnAndRow($key, 1, $value);
        }
        // 写入数据
        $rowNum = 2;
        foreach ($data as $row) {
            foreach ($row as $colNum => $value) {
                $sheet->setCellValueByColumnAndRow($colNum, $rowNum, $value);
            }
            $rowNum++;
        }
        // 创建Excel5写入器
        $objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
        $filePath = $this->config['export_path'] . $filename . '.xls';
        $objWriter->save($filePath);
        if (php_sapi_name() !== 'cli') {
            $this->downloadFile($filePath, true);
        }
        return $filePath;
    }
    /**
     * 导入Excel文件
     * @param string $file 文件路径
     * @param array $mapping 字段映射
     * @return array|false 数据数组或false
     */
    public function importFromExcel($file, $mapping = []) {
        try {
            // 文件校验
            $this->validateFile($file);
            $spreadsheet = null;
            if (class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet')) {
                $reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile($file);
                $spreadsheet = $reader->load($file);
                $rows = $spreadsheet->getActiveSheet()->toArray();
            } else {
                // 使用PHPExcel
                $objPHPExcel = PHPExcel_IOFactory::load($file);
                $rows = $objPHPExcel->getActiveSheet()->toArray();
            }
            if (empty($rows)) {
                return [];
            }
            // 获取表头
            $headers = array_shift($rows);
            // 处理数据
            $data = [];
            foreach ($rows as $rowIndex => $row) {
                $item = [];
                foreach ($headers as $colIndex => $header) {
                    $key = $header;
                    // 如果有映射,则使用映射后的字段名
                    if (!empty($mapping) && isset($mapping[$header])) {
                        $key = $mapping[$header];
                    }
                    $item[$key] = isset($row[$colIndex]) ? $row[$colIndex] : '';
                }
                $data[] = $item;
            }
            return $data;
        } catch (Exception $e) {
            $this->errors[] = '导入失败: ' . $e->getMessage();
            return false;
        }
    }
    /**
     * 导入CSV文件
     * @param string $file 文件路径
     * @param array $mapping 字段映射
     * @param string $delimiter 分隔符
     * @return array|false 数据数组或false
     */
    public function importFromCSV($file, $mapping = [], $delimiter = ',') {
        try {
            // 文件校验
            $this->validateFile($file, ['csv']);
            $data = [];
            $handle = fopen($file, 'r');
            // 读取BOM
            $bom = fread($handle, 3);
            if ($bom != "\xEF\xBB\xBF") {
                rewind($handle);
            }
            // 获取表头
            $headers = fgetcsv($handle, 0, $delimiter);
            // 处理数据
            while (($row = fgetcsv($handle, 0, $delimiter)) !== false) {
                $item = [];
                foreach ($headers as $colIndex => $header) {
                    $key = $header;
                    // 如果有映射,则使用映射后的字段名
                    if (!empty($mapping) && isset($mapping[$header])) {
                        $key = $mapping[$header];
                    }
                    $item[$key] = isset($row[$colIndex]) ? $row[$colIndex] : '';
                }
                $data[] = $item;
            }
            fclose($handle);
            return $data;
        } catch (Exception $e) {
            $this->errors[] = '导入失败: ' . $e->getMessage();
            return false;
        }
    }
    /**
     * 上传文件
     * @param array $file $_FILES中的文件数组
     * @return string|false 上传后的文件路径或false
     */
    public function uploadFile($file) {
        try {
            // 检查上传文件
            if (!isset($file['error']) || is_array($file['error'])) {
                throw new Exception('上传文件错误');
            }
            // 检查错误码
            switch ($file['error']) {
                case UPLOAD_ERR_OK:
                    break;
                case UPLOAD_ERR_NO_FILE:
                    throw new Exception('没有文件被上传');
                case UPLOAD_ERR_INI_SIZE:
                case UPLOAD_ERR_FORM_SIZE:
                    throw new Exception('文件大小超出限制');
                default:
                    throw new Exception('未知错误');
            }
            // 检查文件大小
            if ($file['size'] > $this->config['max_file_size']) {
                throw new Exception('文件大小超出限制');
            }
            // 生成唯一文件名
            $extension = pathinfo($file['name'], PATHINFO_EXTENSION);
            $filename = date('YmdHis') . '_' . uniqid() . '.' . $extension;
            $targetPath = $this->config['import_path'] . $filename;
            // 移动文件
            if (move_uploaded_file($file['tmp_name'], $targetPath)) {
                return $targetPath;
            } else {
                throw new Exception('文件上传失败');
            }
        } catch (Exception $e) {
            $this->errors[] = '上传失败: ' . $e->getMessage();
            return false;
        }
    }
    /**
     * 校验文件
     * @param string $file 文件路径
     * @param array $allowedExtensions 允许的扩展名
     */
    public function validateFile($file, $allowedExtensions = null) {
        if (!file_exists($file)) {
            throw new Exception('文件不存在');
        }
        if (!is_readable($file)) {
            throw new Exception('文件不可读');
        }
        $allowedExtensions = $allowedExtensions ?: $this->config['allowed_extensions'];
        $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if (!in_array($extension, $allowedExtensions)) {
            throw new Exception('文件类型不允许');
        }
        return true;
    }
    /**
     * 分批导出大文件
     * @param callable $callback 数据获取回调函数
     * @param callable $processCallback 处理回调(可选)
     * @param string $filename 文件名
     * @return string|false 
     */
    public function exportLargeData($callback, $processCallback = null, $filename = '') {
        try {
            $filename = $filename ?: date('YmdHis') . '_large_export';
            $filePath = $this->config['export_path'] . $filename . '.csv';
            $file = fopen($filePath, 'w');
            fwrite($file, "\xEF\xBB\xBF"); // 添加BOM
            $page = 1;
            while (true) {
                // 获取数据
                $data = call_user_func($callback, $page, $this->config['chunk_size']);
                if (empty($data)) {
                    break;
                }
                // 是否有处理函数
                if ($processCallback) {
                    $data = call_user_func($processCallback, $data);
                }
                // 写入数据
                foreach ($data as $row) {
                    fputcsv($file, $row);
                }
                $page++;
            }
            fclose($file);
            return $filePath;
        } catch (Exception $e) {
            $this->errors[] = '导出失败: ' . $e->getMessage();
            return false;
        }
    }
    /**
     * 分批导入大文件
     * @param string $file 文件路径
     * @param callable $processCallback 处理每行数据的回调函数
     * @return int|false 处理的行数或false
     */
    public function importLargeData($file, $processCallback) {
        try {
            // 文件校验
            $this->validateFile($file, ['csv']);
            $handle = fopen($file, 'r');
            $rowCount = 0;
            // 读取BOM
            $bom = fread($handle, 3);
            if ($bom != "\xEF\xBB\xBF") {
                rewind($handle);
            }
            // 跳过表头
            fgetcsv($handle, 0, ',');
            // 逐行处理
            while (($row = fgetcsv($handle, 0, ',')) !== false) {
                if (!empty($row)) {
                    // 调用处理函数
                    $result = call_user_func($processCallback, $row);
                    if ($result === false) {
                        break;
                    }
                    $rowCount++;
                }
            }
            fclose($handle);
            return $rowCount;
        } catch (Exception $e) {
            $this->errors[] = '导入失败: ' . $e->getMessage();
            return false;
        }
    }
    /**
     * 下载文件
     * @param string $filePath 文件路径
     * @param boolean $deleteAfter 下载后是否删除
     */
    public function downloadFile($filePath, $deleteAfter = false) {
        if (!file_exists($filePath)) {
            throw new Exception('文件不存在');
        }
        $filename = basename($filePath);
        // 设置HTTP头
        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('Pragma: public');
        header('Content-Length: ' . filesize($filePath));
        // 输出文件内容
        readfile($filePath);
        if ($deleteAfter) {
            @unlink($filePath);
        }
        exit;
    }
    /**
     * 清理文件名
     * @param string $filename
     * @return string
     */
    private function checkFilename($filename) {
        // 移除非法字符
        $filename = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $filename);
        // 防止路径穿越
        $filename = str_replace(['../', './', '..\\', '.\\'], '', $filename);
        return $filename;
    }
    /**
     * 获取错误信息
     * @return array
     */
    public function getErrors() {
        return $this->errors;
    }
    /**
     * 清除错误信息
     */
    public function clearErrors() {
        $this->errors = [];
    }
    /**
     * 检查是否有错误
     * @return boolean
     */
    public function hasErrors() {
        return !empty($this->errors);
    }
}
?>

服务类封装(业务逻辑层)

<?php
/**
 * 导入导出服务类
 * 提供业务级别的导入导出功能
 */
class DataTransferService {
    private $dte;
    private $withHeaders = 'withHeaders'; // 隐私保护示例
    /**
     * 构造函数
     */
    public function __construct($config = []) {
        $this->dte = new DataExportImport($config);
    }
    /**
     * 通用导出方法
     * @param array $params 导出参数
     * @return array [status, message, data]
     */
    public function export($params) {
        try {
            $type = isset($params['type']) ? strtolower($params['type']) : 'csv';
            $headers = isset($params['headers']) ? $params['headers'] : [];
            $data = isset($params['data']) ? $params['data'] : [];
            $filename = isset($params['filename']) ? $params['filename'] : '';
            // 验证数据
            if (empty($data)) {
                return ['status' => false, 'message' => '没有数据可导出', 'data' => null];
            }
            // 根据类型导出
            if ($type == 'excel') {
                $result = $this->dte->exportToExcel($headers, $data, $filename);
            } else {
                $result = $this->dte->exportToCSV($headers, $data, $filename);
            }
            if ($result !== false) {
                return ['status' => true, 'message' => '导出成功', 'data' => $result];
            }
            return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
        } catch (Exception $e) {
            return ['status' => false, 'message' => $e->getMessage(), 'data' => null];
        }
    }
    /**
     * 生成Excel模板
     * @param array $headers 表头
     * @param string $filename 文件名
     * @return array
     */
    public function exportTemplate($headers, $filename = '导入模板') {
        try {
            // 添加示例数据
            $exampleData = [];
            if ($this->isAssocArray($headers)) {
                // 如果是关联数组,只有键作为表头
                $headers = array_keys($headers);
            }
            // 生成示例数据(空一行)
            foreach ($headers as $header) {
                $exampleData[] = ['']; // 空示例行
            }
            // 创建模板
            $result = $this->dte->exportToExcel($headers, $exampleData, $filename);
            if ($result !== false) {
                return ['status' => true, 'message' => '模板生成成功', 'data' => $result];
            }
            return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
        } catch (Exception $e) {
            return ['status' => false, 'message' => $e->getMessage(), 'data' => null];
        }
    }
    /**
     * 通用导入方法
     * @param array $params 导入参数
     * @return array [status, message, data]
     */
    public function import($params) {
        try {
            // 上传文件
            if (isset($params['file'])) {
                $filePath = $this->dte->uploadFile($params['file']);
                if ($filePath === false) {
                    return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
                }
            } elseif (isset($params['path'])) {
                $filePath = $params['path'];
            } else {
                return ['status' => false, 'message' => '没有找到文件', 'data' => null];
            }
            // 获取文件类型
            $type = isset($params['type']) ? strtolower($params['type']) : 
                    strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
            // 字段映射
            $mapping = isset($params['mapping']) ? $params['mapping'] : [];
            // 根据类型导入
            if ($type == 'csv') {
                $data = $this->dte->importFromCSV($filePath, $mapping);
            } else {
                $data = $this->dte->importFromExcel($filePath, $mapping);
            }
            if ($data !== false) {
                return ['status' => true, 'message' => '导入成功', 'data' => $data];
            }
            return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
        } catch (Exception $e) {
            return ['status' => false, 'message' => $e->getMessage(), 'data' => null];
        }
    }
    /**
     * 导入并处理(适用于文件导入数据库)
     * @param string $filePath 文件路径
     * @param string $type 文件类型
     * @param callable $processor 处理函数
     * @return int|false 处理成功条数
     */
    public function importAndProcess($filePath, $type, $processor) {
        try {
            // 使用分批导入处理大文件
            $result = $this->dte->importLargeData($filePath, function($row) use ($processor) {
                // 调用处理函数
                return call_user_func_array($processor, [$row]);
            });
            return $result;
        } catch (Exception $e) {
            return false;
        }
    }
    /**
     * 检查数组是否为关联数组
     * @param array $arr
     * @return boolean
     */
    private function isAssocArray(array $arr) {
        if (empty($arr)) {
            return false;
        }
        return array_keys($arr) !== range(0, count($arr) - 1);
    }
    /**
     * 获取错误信息
     * @return string
     */
    private function getErrorMessage() {
        $errors = $this->dte->getErrors();
        return $errors ? implode('; ', $errors) : '操作失败';
    }
}
?>

实用示例

<?php
/**
 * 使用示例
 */
// 配置
$config = [
    'export_path' => dirname(__FILE__) . '/exports/',
    'import_path' => dirname(__FILE__) . '/imports/',
    'max_file_size' => 20971520, // 20MB
    'chunk_size' => 500,
];
// 1. 基础使用 - 导出
$dte = new DataExportImport($config);
$headers = ['ID', '姓名', '邮箱', '电话'];
$data = [
    [1, '张三', 'zhangsan@example.com', '13800000001'],
    [2, '李四', 'lisi@example.com', '13800000002'],
    [3, '王五', 'wangwu@example.com', '13800000003'],
];
// 导出Excel
$result = $dte->exportToExcel($headers, $data, '用户列表');
// 导出CSV
$result = $dte->exportToCSV($headers, $data, '用户列表');
// 2. 使用服务类 - 更高级的封装
$service = new DataTransferService($config);
$params = [
    'type' => 'excel',
    'headers' => ['ID', '名称', '价格'],
    'data' => [[1, '商品A', 99.9], [2, '商品B', 199.9]],
    'filename' => '商品列表'
];
$result = $service->export($params);
// 3. 导入示例
$file = [
    'error' => UPLOAD_ERR_OK,
    'size' => 1024 * 1024,
    'tmp_name' => '/tmp/phpXXXX',
    'name' => 'test.xlsx'
];
$params = [
    'file' => $file,
    'type' => 'xlsx',
    'mapping' => [
        '姓名' => 'name',
        '邮箱' => 'email',
        '电话' => 'phone'
    ]
];
$result = $service->import($params);
// 4. 大批量数据处理
$dte->exportLargeData(
    function($page, $chunkSize) {
        // 从数据库获取数据
        $data = getDataFromDB($page, $chunkSize);
        return $data;
    },
    null,
    '大量数据导出'
);
// 5. 异常处理
if ($dte->hasErrors()) {
    echo "错误信息: ";
    print_r($dte->getErrors());
}
?>

Composer依赖

{
    "require": {
        "phpoffice/phpspreadsheet": "^1.24",
        "ext-json": "*",
        "ext-fileinfo": "*"
    }
}

配置文件示例

<?php
return [
    'export_path' => env('EXPORT_PATH', storage_path('exports')),
    'import_path' => env('IMPORT_PATH', storage_path('imports')),
    'max_file_size' => env('MAX_FILE_SIZE', 10485760),
    'allowed_extensions' => ['xls', 'xlsx', 'csv'],
    'chunk_size' => 1000,
    'time_limit' => 300,
    'csv_delimiter' => ',',
    'csv_encoding' => 'utf-8',
];
?>

这套封装提供了:

  1. 灵活的导出功能 - 支持Excel和CSV格式
  2. 可靠的导入功能 - 支持文件上传、格式校验
  3. 大批量数据处理 - 分批处理防止内存溢出
  4. 完善的错误处理 - 明确区分错误类型
  5. 配置灵活 - 可自定义路径、大小限制等

使用时需要安装相应依赖库,推荐使用更好的PhpSpreadsheet库,这个封装类可以根据具体业务需求进行扩展和优化。

抱歉,评论功能暂时关闭!