PHPExcel library hangs with relative "big" files - php

I'm trying to export some records to excel from my MySQL (webserver) and when the query returns >4k records the script hangs the web browser and temporaly the web hosting.
My PHP_version is 5.2.13-pl1-gentoo and the memory_limit configurated in php.ini is 128M
The result excel only have one column and N rows. With 100 or 200 rows the php script runs fine.
This is the php script
<? session_start();
ini_set('memory_limit', '1024M');
set_time_limit(0);
include("include/conexion.php");
require_once 'include/PHPExcel/Classes/PHPExcel.php';
require_once 'include/PHPExcel/Classes/PHPExcel/IOFactory.php';
$objPHPExcel = new PHPExcel();
$objPHPExcel->getProperties()->setCreator("Name")
->setLastModifiedBy("Name")
->setTitle("Listado")
->setSubject("Listado")
->setDescription("Listado.")
->setKeywords("Listado")
->setCategory("Listado");
$query = explode("|",stripcslashes($_POST['query']));
$objPHPExcel->getActiveSheet()->setTitle('List');
$resEmp = mysql_query ($query, $conexion ) or die(mysql_error());
$tot = mysql_num_rows($resEmp);
$num_fields = mysql_num_fields($resEmp);
$fistIndex = $objPHPExcel->getActiveSheet()->getCellByColumnAndRow(0, 1)->getColumn();
$lastIndex = $objPHPExcel->getActiveSheet()->getCellByColumnAndRow($num_campos - 1, 1)->getColumn();
//tittles
for ($e=0;$e < $num_fields;$e++){
$objPHPExcel->getActiveSheet()->setCellValueByColumnAndRow($e, 2, utf8_decode(ucwords(mysql_field_name($resEmp,$e))));
$objPHPExcel->getActiveSheet()->getColumnDimension($objPHPExcel->getActiveSheet()->getCellByColumnAndRow($e, 2)->getColumn())->setAutoSize(true);
}
//color tittles
$objPHPExcel->getActiveSheet()->getStyle( $fistIndex.'1:'.$lastIndex.'2' )->getFill()->setFillType(PHPExcel_Style_Fill::FILL_SOLID)->getStartColor()->setRGB('c5c5c7');
$objPHPExcel->getActiveSheet()->getStyle( $fistIndex.'1:'.$lastIndex.'2' )->getFont()->setBold(true);
if(isset ( $_POST ['mail'] )){
$objPHPExcel->getActiveSheet()->setCellValueByColumnAndRow(0, 2, "Email");
$emails = array();
for ($row = 0; $row < $totEmp; $row++) {
//more than one mail in field separated by ";"
$aux = explode(";", mysql_result($resEmp,$row,$col));
for($i=0; $i<count($aux); $i++){
$cleaned = utf8_encode(strtolower(trim($aux[$i])));
//filter repeated mails
if(!in_array($cleaned, $emails) && $aux[$i] != ""){
$num_rows = $objPHPExcel->getActiveSheet()->getHighestRow();
$objPHPExcel->getActiveSheet()->insertNewRowBefore($num_rows + 1, 1);
array_push($emails, $cleaned);
$objPHPExcel->getActiveSheet()->setCellValueByColumnAndRow(0, $num_rows + 1, $cleaned);
}
}
}
}
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
header('Content-type: application/vnd.ms-excel');
header("Content-Disposition: attachment; filename=".$nom_archivo.".xlsx");
// Write file to the browser
$objWriter->save('php://output');
exit();
?>
When enter to the script run a mysql query and then, iterate the result to get the mail field, if the obtained mail not exist in a array this mail is inserted in excel
I've tried to set
ini_set('memory_limit', '1024M');
set_time_limit(0);
But the problem persist.
Any idea to solve problem?
Thanks a lot
EDIT 1
I've updated the code with the recommendations and now works fine.
Anyway How can I get if occurs any error or the memory usage just before of hanging?
How can I get the max memory_limit available to set with ini_set('memory_limit', '2048M'); ?
<? session_start();
ini_set('memory_limit', '2048M');
set_time_limit(0);
include("include/conexion.php");
require_once 'include/PHPExcel/Classes/PHPExcel.php';
require_once 'include/PHPExcel/Classes/PHPExcel/IOFactory.php';
$objPHPExcel = new PHPExcel();
$objPHPExcel->getProperties()->setCreator("Name")
->setLastModifiedBy("Name")
->setTitle("Listado")
->setSubject("Listado")
->setDescription("Listado.")
->setKeywords("Listado")
->setCategory("Listado");
$activeSheet = $objPHPExcel->getActiveSheet();
$query = explode("|",stripcslashes($_POST['query']));
$activeSheet->setTitle('List');
$resEmp = mysql_query ($query, $conexion ) or die(mysql_error());
$tot = mysql_num_rows($resEmp);
$num_fields = mysql_num_fields($resEmp);
$fistIndex = $activeSheet->getCellByColumnAndRow(0, 1)->getColumn();
$lastIndex = $activeSheet->getCellByColumnAndRow($num_campos - 1, 1)->getColumn();
//tittles
for ($e=0;$e < $num_fields;$e++){
$activeSheet->setCellValueByColumnAndRow($e, 2, utf8_decode(ucwords(mysql_field_name($resEmp,$e))));
$activeSheet->getColumnDimension($activeSheet->getCellByColumnAndRow($e, 2)->getColumn())->setAutoSize(true);
}
//color tittles
$activeSheet->getStyle( $fistIndex.'1:'.$lastIndex.'2' )->getFill()->setFillType(PHPExcel_Style_Fill::FILL_SOLID)->getStartColor()->setRGB('c5c5c7');
$activeSheet->getStyle( $fistIndex.'1:'.$lastIndex.'2' )->getFont()->setBold(true);
if(isset ( $_POST ['mail'] )){
$activeSheet->setCellValueByColumnAndRow(0, 2, "Email");
$emails = array();
for ($row = 0; $row < $totEmp; $row++) {
//more than one mail in field separated by ";"
$aux = explode(";", mysql_result($resEmp,$row,$col));
for($i=0; $i<count($aux); $i++){
$cleaned = utf8_encode(strtolower(trim($aux[$i])));
//filter repeated mails
if(!in_array($cleaned, $emails) && $aux[$i] != ""){
array_push($emails, $cleaned);
}
}
}
for ($row = 0; $row < count($emails); $row++) {
$activeSheet->setCellValueByColumnAndRow(0, $row + 3, $emails[$row]);
}
}
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
header('Content-type: application/vnd.ms-excel');
header("Content-Disposition: attachment; filename=".$nom_archivo.".xlsx");
// Write file to the browser
$objWriter->save('php://output');
exit();
?>

Seems this library has serious problem in parsing large excel spreadsheets, I'd this issue already & I couldn't find a proper solution. I guess this is normal behaviour because this library is written fully in PHP that causes a lot of parsing overhead.
I strongly suggest you to use a excel parsing PHP-extension like this one.
As another thinkable solution [if its possible], you can break down your big file to several smaller files (e.g by sheets), otherwise I guess you should use a faster CPU or use another library or programming language to parse your exel files (e.g. apache-poi in java, maybe with a PHP/Java bridge).

Unfortunately, PHPExcel is not good for performing with large data because PHP is not really a good binary file processing language.
Some people export their data to XML format of excel (http://en.wikipedia.org/wiki/Microsoft_Office_XML_formats) and it can work well. However, the xml format does not have full features of excel binary file and of course it will have a bigger file size.
In order to work with the large data (import/export to binary excel file), our system now using libxl which will cost you 199$ for a license, and php_excel which is a wrapper for libxl. In effect, our system now export a excel file with more than 5k of rows in about just only some seconds using libxl and I think it's an only solution for you until now to use binary excel.
P/s: The $objPHPExcel->getActiveSheet() also have a cost, so you could store it value to a variable for reusing later which will help you to speed up your code a little bit.

I had this problem but after changed some options in php.ini and scripts, I could reduce file from 28 MB to 4 MB.
increase memory_limit=2048M in php.ini.
change max_execution_time to more seconds.
in the script yo should use Excel2007 like below:
ob_end_clean();
header('Content-Type: application/vnd.ms-excel');
header("Content-Disposition: attachment;filename=$date.xls");
header('Cache-Control: max-age=0');
ob_end_clean();
$objWriter =PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->save('php://output');

Related

Taking too long to export excel using spout library with php & mysqli?

I am facing a problem at the time of generating excel using spout library. It is taking forever to complete. I have a large amount of data to export. This is going to generate at least 600 rows and 98 columns in excel.
require(APPPATH .'libraries/spout/src/Spout/Autoloader/autoload.php');
$this->load->model('Report_model','report');
$pay_ele = $this->report->pay_elements_data();// iT has 70+ codes like
hra,Da etc.
$pay_code = '';
$array = [];
array_push($array,'E-
ID','Name','Month','Year','Department','Grade','Accno','Bank
Name','IFSC','Aadhar','PFNo','ESI','PAN','Working
Days','Holidays','Weekoffs','Presents','Leaves','Absents');
foreach( $pay_ele as $pay) {
array_push($array,$pay->code);
}
array_push($array, 'Gross Pay');
array_push($array, 'Net Pay');
$writer->addRowWithStyle($array, $style);
if(empty($employee_id)) {
$employees = $this->report->find_all_employee_ids();
}
foreach ( $employees as $emp) {
$employee_id = $emp->employee_id;
$name = $emp->full_name;
$department = $emp->company_department;
$grades = $emp->category_code;
$bank_details = $this->report->find_bank_details($employee_id);
$p_det = $this->report->personal_detail($employee_id);
$mon_dat = $this->report->find_all_monhtlydata($employee_id,$month,$year);
if(!empty($mon_dat)) {
foreach ( $mon_dat as $mondata) {
$absent_days = $mondata->absent_days;
$present_days = $mondata->present_days;
$working_days = $mondata->working_days;
$holidays = $mondata->holidays;
$week_off = $mondata->week_off;
$leave_days = $mondata->leave_days;
}
}
foreach( $bank_details as $bank) {
$acc_number = $bank->account_number;
$bank_name = $bank->bank_name;
$ifsc_code = $bank->ifsc_code;
}
$data1 = [$employee_id,$name,$month,$year,$department,$grades,$acc_number,
$bank_name,$ifsc_code,$addhar,$pf,$esi,$pan,$working_days];
$gtotAmt = 0;
$totAmt = 0;
foreach( $pay_ele as $pay) {
$n_data = $this->report->find_pay_ledger_data_withparametrs
($employee_id,$month,$year,$department,$pay->code);
$amt = 0;
foreach( $n_data as $dat) {
$amt = round($dat->amount,2);
$totAmt += round($dat->amount,2);
if($pay->type == 'ADDITION') {
$gtotAmt += round($dat->amount,2);
} }
array_push($data1,$amt);
}
array_push($data1,$gtotAmt);
array_push($data1,$totAmt);
$writer->addRow($data1);
}
$writer->close();
if (file_exists($filePath)
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment;
filename="'.basename($filePath).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit;
}
I want to export excel in very fast mode but actually it is taking more than 2 hours. For few employees it is generating excel very fast but not for 500+ employees.
600 rows, 100 columns = 60,000 cells. According to the doc, it should not take more than a few seconds to generate your spreadsheet.
So I'm pretty sure the long time it takes has something to do with the code around Spout (maybe double check how data is fetched?). You can add logs to see where time is spent in your program.
Also, instead of using $writer->openToFile($filePath) and read the file to send it to the browser, you can use $writer->openToBrowser($fileName) directly. No need to set extra headers.
You should start looking for bottlenecks.600 rows is not to match. Are you sure if the problem is in the excel library? You will try to measure the time in any part of the application.

Getting OOM from large dataset

I'm trying to create a spreadsheet (XLSX) from a array containing 60k entries. When it exports, it gives me a spreadsheet with a PHP OOM warning on it nothing else.
$spreadsheet = new Spreadsheet();
$spreadsheet->setActiveSheetIndex(0);
$activeSheet = $spreadsheet->getActiveSheet();
$rowIndex = 2;
foreach ($this->_values as $_val){
$activeSheet->setCellValueByColumnAndRow(1, $rowIndex, trim($_val['item1']));
$activeSheet->setCellValueByColumnAndRow(2, $rowIndex, trim($_val['item2']));
$activeSheet->setCellValueByColumnAndRow(3, $rowIndex, trim($_val['item3']));
$activeSheet->setCellValueByColumnAndRow(4, $rowIndex, trim($_val['item4']));
$activeSheet->setCellValueByColumnAndRow(5, $rowIndex, trim($_val['item5']));
$activeSheet->setCellValueByColumnAndRow(6, $rowIndex, trim($_val['item6']));
$activeSheet->setCellValueByColumnAndRow(7, $rowIndex, trim($_val['item7']));
$activeSheet->setCellValueByColumnAndRow(8, $rowIndex, trim($_val['item8']));
$activeSheet->setCellValueByColumnAndRow(9, $rowIndex, trim($_val['item9']));
$activeSheet->setCellValueByColumnAndRow(10, $rowIndex, trim($_val['item10']));
$activeSheet->setCellValueByColumnAndRow(11, $rowIndex, trim($_val['item11']));
$rowIndex += 1;
}
$spreadsheet->garbageCollect();
$writer = new Xlsx($spreadsheet);
$writer->setPreCalculateFormulas(false);
$writer->setUseDiskCaching(true);
$writer->save("php://temp");
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
An out of memory error is because your system is, well, out of memory. You can simply increase the amount of memory using something like ini_set('memory_limit', '750M'); assuming your system has the space
Or, if XLSX isn't needed (that is: if you could use a CSV instead, which Excel can still open) you could stream the data to the spreadsheet instead of loading it all into memory at once, then printing. That would look like this:
foreach ($this->_values as $_val){
for($i=1; $i<=11; $i++) {
echo trim($_val['item'.$i]);
if($i<11) echo ",";
}
echo "\n";
}
You could also include the CSV header at the top of the PHP file, so the page would prompt a download instead of showing you the contents of the CSV file. This is the CSV header cal: header("Content-type: text/csv");

creating multiple csv files from php loop

Im trying to create a loop that when executed it created multiple csv files and downloads them. This is my code:
session_start();
require '../connect.php'; //connect.php has connection info for my database
// and uses the variable $connect
$sqldept = "SELECT department_name from department;";
$departments = mysqli_query($connect, $sqldept);
while ($department = mysqli_fetch_array($departments)) {
$department = $department[0];
header('Content-Type: text/csv; charset=utf-8');
header("Content-Transfer-Encoding: UTF-8");
header('Content-Disposition: attachment; filename=summary-' . $department . '.csv');
header("Cache-Control: no-cache, no-store, must-revalidate"); // HTTP 1.1
header("Pragma: no-cache"); // HTTP 1.0
header("Expires: 0"); // Proxies
$date = date("Y-m-d", strtotime("-28 days" . date("Y-m-d")));
$edate = date("Y-m-d");
$startdate = "(time.dateadded BETWEEN '$date' AND '$edate') AND";
$department = " and department_name = '$department'";
// create a file pointer connected to the output stream
$output = fopen('php://output', 'w');
// output the column headings
$sql2 = "SELECT time.id as timeid, time.staff_id, SUM(time.timein), COUNT(NULLIF(time.reasonforabsence,'')) AS count_reasonforabsence, GROUP_CONCAT(CONCAT(NULLIF(time.reasonforabsence,''),' ', date_format(time.dateadded, '%d-%m-%Y'),' ')) AS reasonforabsence, time.dateadded, staff.id AS staffid, department.id AS departmentid, department.department_name, staff.staff_name, staff.department_id, SUM(staff.workhoursperday), staff.payrollnum FROM time, staff, department WHERE $startdate staff.id = time.staff_id AND staff.department_id = department.id $department $staffsearch GROUP BY staff.id ORDER BY `time`.`dateadded` ASC;";
// output headers so that the file is downloaded rather than displayed
fputcsv($output, array(
'Payroll Number',
'Name',
'Department',
'Hours Worked',
'Days Absent',
'Overtime',
'Reasons for Absence'
));
$rows = mysqli_query($connect, $sql2);
while ($rowcsv = mysqli_fetch_assoc($rows)) {
$reasonforabsence = $rowcsv['reasonforabsence'];
//$reasonforabsence = explode( ',', $rowcsv['reasonforabsence'] );
$overtime = 0;
if (empty($rowcsv['SUM(time.timein)']) == true) {
$rowcsv['SUM(time.timein)'] = 0;
}
;
if ($rowcsv['SUM(time.timein)'] > $rowcsv['SUM(staff.workhoursperday)']) {
$overtime = $rowcsv['SUM(time.timein)'] - $rowcsv['SUM(staff.workhoursperday)'];
}
;
fputcsv($output, array(
$rowcsv['payrollnum'],
$rowcsv['staff_name'],
$rowcsv['department_name'],
$rowcsv['SUM(time.timein)'],
$rowcsv['count_reasonforabsence'],
$overtime,
$reasonforabsence
));
};
readfile("php://output");
fclose($output);
};
Currently the loop created 1 CSV with a new header and the department details below it like this
I want the loop to create a new CSV for each department but its just not working for me. Any help is appreciated.
Thanks
Unfortunately you can't, 1 PHP Request results in one file, and there isn't really a way around this. You can, however, try to download them all as a ZIP file. Take a look at this question f.e.
The below are some workaround ideas, which might be useful in certain scenarios (and might be dangerous in other scenarios). Use under your own risk!
Workaround A: Loop by redirect
Output a single file normally
Do a redirect to same url that's creating the CSV file in step#1, but append a GET flag to that, like http://www.example.net/output_csv?i=1
Make sure to add a loop-breaker in step#1, like if($i==10) { exit; }
Workaround B: Loop by cronjob
Output a single file normally
Make 2nd file output be handled by a separate cronjob call.
Make sure to add a loop-breaker in step#1, like if($mycron==10) { exit; }
You can not do this by for loop.
However, You can make a php file which can do your purpose.
<a onclick="getcsv()" href="php_file_location.php?table_name=test"> Download </a>
<script>
function getcsv() {
window.open(php_file_location);
}
</script>
I was in the same problem as mentioned. But in my case I was not trying to download multiple CSVs but I was uploading it to sFTP server. While creating the file instead of using
$output = fopen('php://output', 'w');
I used
$output = fopen($path_and_name, 'w');
where $path_and_name = $path_to_sftp_folder.'/'.$file_name;
after the execution the correct file was uploaded to there respective folders correctly the way I wanted it to be. But yes the wrong file was also downloaded with same issue as sent above.
So if you are looking for uploading files on a server it can be done(even if they all have same name).

Exporting data to excel (multiple sheets) using php

I am trying to export data to Excel using this PHP Class, so far things are working fine and the export is being generated. But now I have a new requirement of generating multiple sheets inside a single excel file.
For example if i have two arrays, i want both to be on separate sheets.
$myarray1 = array (
1 => array ("Oliver", "Peter", "Paul"),
array ("Marlene", "Mica", "Lina")
);
$myarray2 = array (
1 => array ("Oliver", "Peter", "Paul"),
array ("Marlene", "Mica", "Lina")
);
At present both arrays are being exported on a single sheet
$xls = new Excel_XML;
$xls->addArray ( $myarray );
$xls->addArray ( $myarray2 );
$xls->generateXML ( "testfile" );
I am wondering if someone tried this before and was able to achieve it and I will appreciate any help I can get on this.
i would suggest you to use PHPExcel library.supports variety of formats, can do visual formatting and is easy to use.
You can find more about it at their webpage: http://phpexcel.codeplex.com/
You can do a lot more of course, reading excel files, setting visual styles, creating plots, expressions and lot more.
you can even use fgetcsv http://php.net/manual/en/function.fgetcsv.php
this example using PHPExcel
function exportToExcelsheets($data, $fileName){
/* Create new PHPExcel object*/
$objPHPExcel = new PHPExcel();
$sheet_index = 0;
foreach ($data as $s=>$sheet){
/* Create a first sheet, representing sales data*/
$alpha = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','V','W','X','Y','Z'];
$objPHPExcel->setActiveSheetIndex($sheet_index);
$head_keys = array_keys($sheet[0]);
foreach ($head_keys as $a=>$headval){
$objPHPExcel->getActiveSheet()->setCellValue($alpha[$a].'1', $headval);
}
$i=2;
foreach($sheet as $row) {
$index = 0;
foreach ($row as $v=>$value){
$value = isset($value)?$value:'';
$objPHPExcel->getActiveSheet()->setCellValue($alpha[$index].$i,$value);
$index++;
}
$i++;
}
/*Rename sheet*/
$objPHPExcel->getActiveSheet()->setTitle('sheet_'.$s);
/* Create a new worksheet, after the default sheet*/
$objPHPExcel->createSheet();
$sheet_index++;
}
/* Redirect output to a client’s web browser (Excel5)*/
header('Content-Type: application/vnd.ms-excel');
header("Content-Disposition: attachment; filename=\"$fileName\"");
header('Cache-Control: max-age=0');
//$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
//$objWriter->save('php://output');
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->save('php://output');
}

PHPExcel generating totally jacked up output

Greetings,
I am having trouble figuring out how to properly use PHP in general and PHPExcel in particular. I have read multiple posts on this topic and yet I've been running around in circles. Here is the relevant portion of my jacked up code:
$viewinv = mysql_connect($sqlsrv,$username,$password);
if (!$viewinv) { die('Could not connect to SQL server. Contact administrator.'); }
mysql_select_db($database, $viewinv) or die('Could not connect to database. Contact administrator.');
$query = "select unit_id,config,location from inventory;";
$result = mysql_query($query);
if ($result = mysql_query($query) or die(mysql_error())) {
$objPHPExcel = new PHPExcel();
$objPHPExcel->getActiveSheet()->setTitle('blah');
$rowNumber = 1;
$headings = array('Unit ID','Config','Location');
$objPHPExcel->getActiveSheet()->fromArray(array($headings),NULL,'A'.$rowNumber);
$rowNumber++;
while ($row = mysql_fetch_row($result)) {
$col = 'A';
foreach($row as $cell) {
$objPHPExcel->getActiveSheet()->setCellValue($col.$rowNumber,$cell);
$col++;
}
$rowNumber++;
}
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="myFile.xls"');
header('Cache-Control: max-age=0');
$objWriter->save('php://output');
exit();
}
echo 'a problem has occurred... no data retrieved from the database';
PHPExcel is definitely outputting data from the query, I can see bits and pieces of plaintext, but it is surrounded by a ton of random characters as if though I am looking at the contents of a compressed or compiled piece of data.
For example:
PKâh¿>G’D²Xð[Content_Types].xml­”MNÃ0…÷œ"ò%nY „švAa •(0ö¤±êØ–gúw{&i‰#ÕnbEö{ßøyìÑdÛ¸l mð¥‘×ÁX¿(ÅÛü)¿’òF¹à¡;#1_滘±Øc)j¢x/%ê…Eˆày¦
Any pointers would be extremely appreciated
Your problem is certainly in outputting more content than just Excel data (which is contained in output buffer).
To solve your problem, just call
ob_clean(); //this will clean the output buffer
before sending header.
The problem will likely be resolved by matching the correct writer types to the correct content-types and file extension.
XLSX (office 2007+):
Writer : Excel2007 (PHPExcel_Writer_Excel2007)
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
XLS (before office 2007):
Writer : Excel5 (PHPExcel_Writer_Excel5)
Content-Type: application/vnd.ms-excel

Categories