8

我们正在开发和维护几个系统,这些系统需要以 Excel 格式将报告导出给最终用户。这些报告是从 MySQL 数据库中收集的,经过一些简单的处理,通常会产生约 40000 行 10-15 列的数据,我们预计数据量将稳步增长。

目前我们正在使用 PHPExcel 生成 Excel,但它不再适合我们了。当我们超过 5000 行之后,内存消耗和加载时间变得难以忍受,并且无法通过无限期增加 PHP 的内存使用和脚本执行时间的最大限制来解决。数据处理尽可能精简,整个问题在于 PHPExcel 是一个内存猪。CSV 生成会更轻松,但不幸的是,由于用户需求,我们需要从我们的服务中导出 Excel(和单独的 Excel)。这是由于格式要求等原因,因此不能选择 CSV。

对于第三方应用程序/模块/服务/生成大型 excel 的任何想法/建议?不管它是否是商业许可,只要它符合我们的需求,可以集成到现有的 PHP 应用程序中并完成它的工作。我们的服务通常在 linux/php/mysql 上运行,我们几乎可以对服务器做任何我们需要做的事情。

谢谢!

4

6 回答 6

3

对于如此大量的数据,我不推荐 PHPExcel 或 ApachePOI(用于 Java)之类的工具,因为它们需要内存。我最近一直在为类似的任务而苦苦挣扎,我发现了将数据注入电子表格的方便(但可能有点繁琐)的方法。可以在服务器端生成或更新 Excel 电子表格,从而进行简单的 XML 编辑。我在服务器上有 XLSX 电子表格,每次从 dB 收集数据时,我都会使用 php 解压缩它。然后我访问包含需要手动注入和插入数据的工作表内容的特定 XML 文件。之后,我压缩电子表格文件夹,以便将其作为常规 XLSX 文件分发。整个过程非常快速和可靠。显然,与 XLSX/Open XML 文件的内部组织相关的问题和故障很少(例如 Excel 倾向于将所有字符串存储在单独的表中,并在工作表中使用对该表的引用)。但是当只注入像数字和字符串这样的数据时,这并不难。如果有人有兴趣,我可以提供一些代码。

好的,这是示例代码。我试图评论它的作用,但随时要求进一步解释。

<?php
/** 
 * Class for serverside spreadsheet data injecting
 * Reqs: unzip.php, zip.php (containing any utility functions able to unzip files & zip folders)
 *
 * Author: Poborak
 */
class DataInjector
{    
    //spreadsheet file, we inject data into this one
    const SPREADSHEET_FILE="datafile.xlsx";   
    // specific worksheet into which data are being injected    
    const SPREADSHEET_WORKSHEET_FILE="/xl/worksheets/sheet7.xml"; 
    //working directory, spreadsheet is extracted here
    const WSPACE_DIR="Wspace";
    // query for obtaining data from DB
    const STORE_QUERY = "SELECT * FROM stores ORDER BY store_number ASC"; 

    private $dbConn;
    private $storesData;

    /**
     * @param   mysqli  $dbConn
     */
    function __construct(mysqli $dbConn) {   
        $this->dbConn = $dbConn;
    }

    /**
     * Main method for whole injection process
     * First data are gathered from DB and spreadsheet is decompressed to workspace.
     * Then injection takes place and spreadsheet is ready to be rebuilt again by zipping.
     *
     * @return   boolean    Informace o úspěchu
     */     
    public function injectData() {

        if (!$this->getStoresInfoFromDB()) return false;        
        if (!$this->explodeSpreadsheet(self::SPREADSHEET_FILE,self::WSPACE_DIR)) return false;                      
        if (!$this->injectDataToSpreadsheet(self::WSPACE_SUBDIR.self::SPREADSHEET_WORKSHEET_FILE)) return false;            
        if (!$this->implodeSpreadsheet(self::SPREADSHEET_FILE,self::WSPACE_DIR)) return false;
        return true;
    }

    /**
     * Decompress spreadsheet file to folder
     *
     * @param   string  $spreadsheet
     * @param   string  $targetFolder
     *
     * @return   boolean    success/fail 
     */   
    private function explodeSpreadsheet($spreadsheet, $targetFolder) {
        return unzip($spreadsheet,$targetFolder);
    }

    /**
     * Compress source folder to spreadsheet file
     *
     * @param   string  $spreadsheet    
     * @param   string  $sourceFolder
     *
     * @return   boolean    success/fail 
     */   
    private function implodeSpreadsheet($spreadsheet, $sourceFolder) {
        return zip($sourceFolder,$spreadsheet);
    }

    /**
     * Loads data from DB to member variable $storesDetails (as array)
     *
     * @return   boolean    success/fail 
     */ 
    private function getStoresInfoFromDb() {
        unset($this->storesData);       

        if ($stmt = $this->dbConn->prepare(self::STORE_QUERY)) {
            $stmt->execute();
            $stmt->bind_result($store_number, $store_regional_manager, $store_manager, $store_city, $store_address);
            while ($stmt->fetch()) {
                $this->storesData[trim($store_number)] = array(trim($store_regional_manager),trim($store_manager),trim($store_address),trim($store_city));
            }           
            $stmt->close();
        }   
        return true;        
    }

    /**
     * Injects data from member variable $storesDetails to spreadsheet $ws
     *
     * @param   string  $ws target worksheet
     *
     * @return   boolean    success/fail
     */ 
    private function injectDataToSpreadsheet($ws) {
         $worksheet = file_get_contents($ws);    
         if ($worksheet === false or empty($this->storesData) return false;

         $xml = simplexml_load_string($worksheet);  
         if (!$xml) return false;

        // Loop through $storesDetails array containing rows of data
        foreach ($this->storesData as $std){

            // For each row of data create new row in excel worksheet
            $newRow = $xml->sheetData->addChild('row'); 

            // Loop through columns values in rowdata
            foreach ($std as $cbd){                      
                // Save each column value into next column in worksheets row 
                 foreach ($this->storesData as $cbd){
                    $newCell = $newRow->addChild('c'); 
                    $newCell->addAttribute('t', "inlineStr");
                    $newIs = $newCell->addChild('is');
                    // text has to be saved as utf-8 (otherwise the spreadsheet file become corrupted)
                    if (!mb_check_encoding($cbd, 'utf-8')) $cbd = iconv("cp1250","utf-8",$cbd); 
                    $newT = $newIs->addChild('t',$cbd);                     
                }
             }
         }

         // Save xml data back to worksheet file
         if (file_put_contents($ws, $xml->asXML()) !== false) return true;           
    }
}
?>   
于 2012-05-31T08:39:13.087 回答
1

我尝试保持最新的 PHPExcel 替代品列表在这里

如果您追求的原始速度/内存性能超出 PHPExcel 所能提供的任何东西,那么我真正推荐的唯一一个是Ilia 的 libXL 包装器扩展因为该库仍然受到积极支持。

于 2012-05-18T12:16:16.087 回答
0

您是否尝试过旧的 Pear Excel(又名 Spreadsheet_Excel_Writer: http: //pear.php.net/package/Spreadsheet_Excel_Writer/redirected)?

关于 Pear Vs PHPExcel 的结帐讨论:http:
//phpexcel.codeplex.com/discussions/240688

于 2012-05-18T08:21:21.650 回答
0

您可以以 CSV 格式导出,Excel 可以处理。如果您在写入文件时遇到问题,您可以随时循环结果(分页)并将它们附加到 CSV 文件中

之后尝试使用 PHPExcel 将其转换为 .xsl 或 .odf 格式,否则将其保留为 CSV。

于 2012-05-18T08:21:33.873 回答
0

查看OfficeWriter。我们最近专门为一家财富 500 强金融公司改进了海量数据集的性能。它对文件格式的作用比您特别需要的要多(图表和您拥有的东西),但是 API 非常易于使用,并且通过评估,您可以快速获得 POC。免责声明 - 我支持构建最新版本的工程师。

你们的另一个缺点是它是.NET。

于 2012-05-18T15:31:32.967 回答
0

只是打印表呢?

<?php
header("Content-Type:   application/vnd.ms-excel; charset=utf-8");
header("Content-Disposition: attachment; filename=abc.xls");  //File name extension was wrong
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);

echo "<table><tr><td>Test</td><td>Test2</td></table>";
于 2013-06-07T12:35:45.347 回答