PHPExcel 是一個php語言讀取導出數據、導入生成Excel的類庫,使用起來非常方便,但有時會遇到以些問題,比如導出的數據超時,內存溢出等。
下面我們來說說這些問題和解決辦法。
PHPExcel 版本:@version 1.8.0, 2014-03-02
能遇到這樣的問題一般都是因為數據量大導致
1.PHPExcel 報錯
報錯提示:
'break' not in the 'loop' or 'switch' context
嚴格的講這個不是PHPExcel的錯誤,是PHP版本的問題,大於PHP5.6以后,“break”必須要在循環體內執行(for ,foreach, while, switch)
此處無循環,解決辦法:注釋掉break;
2.超時
提示:
Maximum execution time of 30 seconds exceeded
數據量過大,php執行超過30秒后就會報這樣的信息
解決辦法:
可修改php.ini 或直接在執行頁面中添加
set_time_limit(0);
這樣就設置了php的執行超時
3.內存溢出
超時解決好之后,等待了好幾十秒后又來了個錯誤:
Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
內存不足呀!
解決內存溢出我們分兩步走,
第一步:設置memory_limit
默認情況memory_limit 大小為100MB,當所需內存大於100MB就會溢出,所以設置足夠大的值
ini_set("memory_limit", "1024M"); // 根據電腦配置不夠繼續增加
第二步:設置PHPExcel單元格緩存
單元格緩存是將所需PHPExcel內存單元格對象緩存到磁盤、memcache、MemoryGZip等,這樣讀取上會更耗時,但可以降低內存的消耗。
PHPExcel_CachedObjectStorageFactory 這個類中提供了這幾個單元格緩存
const cache_in_memory = 'Memory'; const cache_in_memory_gzip = 'MemoryGZip'; #將單元格序列化后再進行Gzip壓縮,然后保存在內存中 const cache_in_memory_serialized = 'MemorySerialized'; # 將單元格數據序列化后保存在內存中 const cache_igbinary = 'Igbinary'; #存儲為緊密的二進制形式 const cache_to_discISAM = 'DiscISAM'; #緩存在臨時的磁盤文件中,速度可能會慢一些 const cache_to_apc = 'APC'; #Alternative PHP Cache可選PHP緩存 const cache_to_memcache = 'Memcache'; #保存在memcache中 const cache_to_phpTemp = 'PHPTemp'; #保存在php://temp const cache_to_wincache = 'Wincache'; const cache_to_sqlite = 'SQLite'; const cache_to_sqlite3 = 'SQLite3';
每一個worksheet都會有一個獨立的緩存,當一個worksheet實例化時,就會根據設置或配置的緩存方式來自動創建。一旦你開始讀取一個文件或者你已經創建了第一個worksheet,就不能在改變緩存的方式了。
- MemorySerialized: 使用這種緩存方式,單元格會以序列化的方式保存在內存中,這是降低內存使用率性能比較高的一種方案。
-
MemoryGZip: 與序列化的方式類似,這種方法在序列化之后,又進行gzip壓縮之后再放入內存中,這回跟進一步降低內存的使用,但是讀取和寫入時會有一些慢。
-
DiscISAM:當使用cache_to_discISAM這種方式時,所有的單元格將會保存在一個臨時的磁盤文件中,只把他們的在文件中的位置保存在PHP的內存中,這會比任何一種緩存在內存中的方式都慢,但是能顯著的降低內存的使用。臨時磁盤文件在腳本運行結束是會自動刪除。
-
PHPTemp: 類 似cache_to_discISAM這種方式,使用cache_to_phpTemp時,所有的單元格會還存在php://temp I/O流中,只把 他們的位置保存在PHP的內存中。PHP的php://memory包裹器將數據保存在內存中,php://temp的行為類似,但是當存儲的數據大小超 過內存限制時,會將數據保存在臨時文件中,默認的大小是1MB,但是你可以在初始化時修改它。php://temp文件在腳本結束是會自動刪除。
4.大數據導出
微軟的Excel設置單元格行數默認是6萬行rows,相對的講其實當我們超過1萬行的時候已經是大數據的導出。
好比:有客戶10000人,平均每人每天產生10條活動記錄,要導出上周所有的客戶活動記錄: 10000*10*7=700000
估計看70萬行的Excel這個人會瘋掉的,我們的建議是分批次導出,按時間導出到不同的excel
下面是一個PHPExcel官方的Demo(已修改過)
define('EOL', '<br />'); $objPHPExcel = new \app\extensions\PHPExcel\PHPExcel(); ini_set("memory_limit", "1024M"); // 設置php可使用內存 $cacheMethod = \PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip; if (!\PHPExcel_Settings::setCacheStorageMethod($cacheMethod)) { die($cacheMethod . " 緩存方法不可用" . EOL); } echo date('H:i:s'), " 當前使用的緩存方法是: ", $cacheMethod, " 方式", EOL; echo date('H:i:s'), " 開始設置文檔屬性", EOL; $objPHPExcel->getProperties()->setCreator("Maarten Balliauw") ->setLastModifiedBy("Maarten Balliauw") ->setTitle("Office 2007 XLSX Test Document") ->setSubject("Office 2007 XLSX Test Document") ->setDescription("Test document for Office 2007 XLSX, generated using PHP classes.") ->setKeywords("office 2007 openxml php") ->setCategory("Test result file"); echo date('H:i:s'), " 開始添加單元格標題", EOL; $objPHPExcel->setActiveSheetIndex(0); $objPHPExcel->getActiveSheet()->setCellValue('A1', "Firstname"); $objPHPExcel->getActiveSheet()->setCellValue('B1', "Lastname"); $objPHPExcel->getActiveSheet()->setCellValue('C1', "Phone"); $objPHPExcel->getActiveSheet()->setCellValue('D1', "Fax"); $objPHPExcel->getActiveSheet()->setCellValue('E1', "Is Client ?"); // 設置單元格寬度 $objPHPExcel->getActiveSheet()->getColumnDimension('A')->setAutoSize(true); $objPHPExcel->getActiveSheet()->getColumnDimension('B')->setWidth(50); /** * 左對齊與 右對齊 * 可設置整列->getStyle('N') 可針對行rows設置getStyle('N3') */ $objPHPExcel->getActiveSheet()->getStyle('B')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_RIGHT); $objPHPExcel->getActiveSheet()->getStyle('B3')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT); echo date('H:i:s'), " 設置隱藏C D列", EOL; $objPHPExcel->getActiveSheet()->getColumnDimension('C')->setVisible(false); $objPHPExcel->getActiveSheet()->getColumnDimension('D')->setVisible(false); echo date('H:i:s'), " 設置大綱級別", EOL; $objPHPExcel->getActiveSheet()->getColumnDimension('E')->setOutlineLevel(1) ->setVisible(false) ->setCollapsed(true); /** * 開始添加數據 */ for ($i = 2; $i <= 50000; $i++) { $objPHPExcel->getActiveSheet()->setCellValue('A' . $i, "FName $i") ->setCellValue('B' . $i, "LName $i") ->setCellValue('C' . $i, "PhoneNo $i") ->setCellValue('D' . $i, "FaxNo $i") ->setCellValue('E' . $i, true); } $objPHPExcel->getActiveSheet()->setTitle('供應商信息'); echo date('H:i:s'), " 設置格式為Excel2007版格式", EOL; $callStartTime = microtime(true); $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007'); $objWriter->save(str_replace('.php', '.xlsx', __FILE__)); $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; echo date('H:i:s'), " 設置生成的文件為: ", str_replace('.php', '.xlsx', pathinfo(__FILE__, PATHINFO_BASENAME)), EOL; echo date('H:i:s'), ' 寫入Workbook中耗時 ', sprintf('%.4f', $callTime), " 秒", EOL; echo date('H:i:s'), ' 當前內存使用情況: ', (memory_get_usage(true) / 1024 / 1024), " MB", EOL; echo date('H:i:s'), " 內存使用峰值: ", (memory_get_peak_usage(true) / 1024 / 1024), " MB", EOL; echo date('H:i:s'), " 完成寫入文件", EOL; echo date('H:i:s'), ' 文件被創建在: ', getcwd(), '目錄', EOL;
下面模擬一個大數據的導出:
msyql中tcustomer表有數據4萬多條
我們設置
set_time_limit(0); ini_set("memory_limit", "1024M"); \PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip; # 單元格緩存為MemoryGZip
然后導出所有4萬多條數據
代碼如下(Yii2)
/** * PHPExcel 數據導出 */ public function actionPhpexcel() { define('EOL', '<br />'); $objPHPExcel = new \app\extensions\PHPExcel\PHPExcel(); ini_set("memory_limit", "1024M"); // 設置php可使用內存 set_time_limit(0); # 設置執行時間最大值 $cacheMethod = \PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip; if (!\PHPExcel_Settings::setCacheStorageMethod($cacheMethod)) { die($cacheMethod . " 緩存方法不可用" . EOL); } echo date('H:i:s'), " 當前使用的緩存方法是: ", $cacheMethod, " 方式", EOL; echo date('H:i:s'), " 開始設置文檔屬性", EOL; $objPHPExcel->getProperties()->setCreator("dcb3688") ->setLastModifiedBy("dcb3688") ->setTitle("客戶信息記錄") ->setSubject("客戶信息Document") ->setDescription("描述……") ->setKeywords("office 2007 php") ->setCategory("產品信息AAA"); echo date('H:i:s'), " 開始添加單元格標題", EOL; $objPHPExcel->setActiveSheetIndex(0); $objPHPExcel->getActiveSheet()->setCellValue('A1', "客戶姓名"); $objPHPExcel->getActiveSheet()->setCellValue('B1', "性別"); $objPHPExcel->getActiveSheet()->setCellValue('C1', "Province"); $objPHPExcel->getActiveSheet()->setCellValue('D1', "City"); $objPHPExcel->getActiveSheet()->setCellValue('E1', "Town"); $objPHPExcel->getActiveSheet()->setCellValue('F1', "Telephone"); $objPHPExcel->getActiveSheet()->setCellValue('G1', "屬相"); $objPHPExcel->getActiveSheet()->setCellValue('H1', "星座"); $objPHPExcel->getActiveSheet()->setCellValue('I1', "血型"); $objPHPExcel->getActiveSheet()->setCellValue('J1', "Nid"); $objPHPExcel->getActiveSheet()->setCellValue('K1', "Uid"); $objPHPExcel->getActiveSheet()->setCellValue('L1', "Etime"); $objPHPExcel->getActiveSheet()->setCellValue('M1', "Regtime"); $objPHPExcel->getActiveSheet()->setCellValue('N1', "Signup"); $objPHPExcel->getActiveSheet()->setCellValue('O1', "經度"); $objPHPExcel->getActiveSheet()->setCellValue('P1', "緯度"); $objPHPExcel->getActiveSheet()->setCellValue('Q1', "類型"); $objPHPExcel->getActiveSheet()->setCellValue('R1', "狀態"); /** * 單元格寬度 */ $objPHPExcel->getActiveSheet()->getColumnDimension('F')->setAutoSize(true); $objPHPExcel->getActiveSheet()->getColumnDimension('J')->setWidth(45); $objPHPExcel->getActiveSheet()->getColumnDimension('L')->setAutoSize(true); $objPHPExcel->getActiveSheet()->getColumnDimension('M')->setAutoSize(true); $objPHPExcel->getActiveSheet()->getColumnDimension('N')->setAutoSize(true); /** * 左對齊與 右對齊 * 可設置整列->getStyle('N') 可針對行rows設置getStyle('N3') */ $objPHPExcel->getActiveSheet()->getStyle('N')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_RIGHT); $objPHPExcel->getActiveSheet()->getStyle('N3')->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT); #####################################開始添加數據############################################################### /** * 分頁分時間: 微軟Execl最大值6萬行, total/60000=文件個數, limit 60000, 60000 * $model->find()->offset($pages->offset)->limit(60000)->all() */ $model = \app\models\Tcustomer::find()->orderBy('regtime desc')->all(); if (!empty($model)) { foreach ($model as $key => $value) { $Cellkey = $key + 2; $blood = [1 => 'A型', 2 => 'B型', 3 => 'AB型', 4 => 'O型']; $objPHPExcel->getActiveSheet()->setCellValue('A' . $Cellkey, mb_substr($value->realname, 0, -1) . '*'); $objPHPExcel->getActiveSheet()->setCellValue('B' . $Cellkey, rand(1, 2) == 1 ? '先生' : '女士'); $objPHPExcel->getActiveSheet()->setCellValue('C' . $Cellkey, $value->province); $objPHPExcel->getActiveSheet()->setCellValue('D' . $Cellkey, $value->city); $objPHPExcel->getActiveSheet()->setCellValue('E' . $Cellkey, $value->town); $objPHPExcel->getActiveSheet()->setCellValue('F' . $Cellkey, $value->telephone ? substr($value->telephone, 0, 3) . '*********' : ''); $objPHPExcel->getActiveSheet()->setCellValue('G' . $Cellkey, $value->sx); $objPHPExcel->getActiveSheet()->setCellValue('H' . $Cellkey, $value->constel); $objPHPExcel->getActiveSheet()->setCellValue('I' . $Cellkey, $blood[array_rand($blood)]); $objPHPExcel->getActiveSheet()->setCellValue('J' . $Cellkey, mb_substr($value->nid, 0, -3)); $objPHPExcel->getActiveSheet()->setCellValue('K' . $Cellkey, $value->uid); $objPHPExcel->getActiveSheet()->setCellValue('L' . $Cellkey, $value->exp); $objPHPExcel->getActiveSheet()->setCellValue('M' . $Cellkey, $value->regtime); $objPHPExcel->getActiveSheet()->setCellValue('N' . $Cellkey, $value->signup ? $value->signup : '無數據'); $objPHPExcel->getActiveSheet()->setCellValue('O' . $Cellkey, substr($value->lng, 0, 4) . rand(10000, 999999)); $objPHPExcel->getActiveSheet()->setCellValue('P' . $Cellkey, substr($value->lat, 0, 3) . rand(10000, 999999)); $objPHPExcel->getActiveSheet()->setCellValue('Q' . $Cellkey, $value->type == 1 ? '意向客戶' : '觀望中客戶'); $objPHPExcel->getActiveSheet()->setCellValue('R' . $Cellkey, $value->status == 1 ? '已下單' : '已跟進'); } } else { die(" 暫無數據" . EOL); } ####################################################################################################### $objPHPExcel->getActiveSheet()->setTitle('客戶信息'); echo date('H:i:s'), " 設置格式為Excel2007版格式", EOL; $callStartTime = microtime(true); $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007'); $objWriter->save(str_replace('.php', '.xlsx', __FILE__)); $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; echo date('H:i:s'), " 設置生成的文件為: ", str_replace('.php', '.xlsx', pathinfo(__FILE__, PATHINFO_BASENAME)), EOL; echo date('H:i:s'), ' 寫入Workbook中耗時 ', sprintf('%.4f', $callTime), " 秒", EOL; echo date('H:i:s'), ' 當前內存使用情況: ', (memory_get_usage(true) / 1024 / 1024), " MB", EOL; echo date('H:i:s'), " 內存使用峰值: ", (memory_get_peak_usage(true) / 1024 / 1024), " MB", EOL; echo date('H:i:s'), " 完成寫入文件", EOL; echo date('H:i:s'), ' 文件被創建在: ', getcwd(), '目錄', EOL; }
執行后信息:
打開文件: