PHPExcel taking an extremely long time to read Excel file - php

I'm using PHPExcel 1.7.8, PHP 5.4.14, Windows 7, and an Excel 2007 spreadsheet. The spreadsheet consists of 750 rows, columns A through BW, and is about 600KB in size. This is my code for opening the spreadsheet--pretty standard:
//Include PHPExcel_IOFactory
include 'PHPExcel/IOFactory.php';
include 'PHPExcel.php';
$inputFileName = 'C:\xls\lspimport\GetLSP1.xlsx';
// Read your Excel workbook
try {
$inputFileType = PHPExcel_IOFactory::identify($inputFileName);
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load($inputFileName);
} catch(Exception $e) {
die('Error loading file "'.pathinfo($inputFileName,PATHINFO_BASENAME).'": '.$e->getMessage());
}
//set active worksheet
$objWorksheet = $objPHPExcel->setActiveSheetIndexbyName('Sheet1');
$j = 0;
for($i = 2; $i < 3; $i++)
{
...
}
In the end, I eventually want to loop through each row in the spreadsheet, but for the time being while I perfect the script, I'm only looping through one row. The problem is, it takes 30 minutes for this script to execute. I echo'd messages after each section of code so I could see what was being processed and when, and my script basically waits for 30 minutes at this part:
$objPHPExcel = $objReader->load($inputFileName);
Have a configured something incorrectly? I can't figure out why it takes 30 minutes to load the spreadsheet. I appreciate any and all help.

PHPExcel has a problem with identifying where the end of your excel file is. Or rather, Excel has a hard time knowing where the end of itself is. If you touch a cell at A:1000000 it thinks it needs to read that far.
I have done 2 things in the past to fix this:
1) Cut and past the data you need into new excel file.
2) Specify the exact dimensions you want to read.
Edit How to do option 2
public function readExcelDataToArray($excelFilePath, $maxRowNumber=-1, $maxColumnNumber=-1)
{
$objPHPExcel = PHPExcel_IOFactory::load($excelFilePath);
$objWorksheet = $objPHPExcel->getActiveSheet();
//Get last row and column that have data
if ($maxRowNumber == -1){
$lastRow = $objWorksheet->getHighestDataRow();
} else {
$lastRow = $maxRowNumber;
}
if ($maxColumnNumber == -1){
$lastCol = $objWorksheet->getHighestDataColumn();
//Change Column letter to column number
$lastCol = PHPExcel_Cell::columnIndexFromString($lastCol);
} else {
$lastCol = $maxColumnNumber;
}
//Get Data Array
$dataArray = array();
for ($currentRow = 1; $currentRow <= $lastRow; $currentRow++){
for ($currentCol = 0; $currentCol <= $lastCol; $currentCol++){
$dataArray[$currentRow][$currentCol] = $objWorksheet->getCellByColumnAndRow($currentCol,, $currentRow)->getValue();
}
}
return $dataArray;
}
Unfortunately these solutions aren't very dynamic.
Note that a modern excel file is really just a zip with an xlsx extension. I have written extensions to PHPExcel that unzip them, and modify certain xml files to get the kinds of behaviors I want.
A third suggestion for you would be to monitor the contents of each row and stop when you get an empty one.

Resolved (for me) - see note at bottom of this post
I'm trying to use pretty much identical code on a dedicated quad core server with 16GB of RAM, also running similar versions - PHPExcel 1.7.9 and PHP 5.4.16
Just creating an empty reader takes 50 seconds!
// $inputFileType is 'Excel5';
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
Loading the spreadsheet (1 sheet, 2000 rows, 25 columns) I want to process (readonly) then takes 1802 seconds.
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load($inputFileName);
Of the various types of reader I consistently get timings for instantiation as shown below
foreach(array(
'Excel2007', // 350 seconds
'Excel5', // 50 seconds
'Excel2003XML', // 50 seconds
'OOCalc', // 50 seconds
'SYLK', // 50 seconds
'Gnumeric', // 50 seconds
'HTML', // 250 seconds
'CSV' // 50 seconds
) as $inputFileType) {
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
}
Peak memory usage was about 8MB... far less than the 250MB the script has available to it.
My suspicion WAS that PHPExcel_IOFactory::createReader($inputFileType) was calling something within a loop that's extremely slow under PHP 5.4.x ?
However the excessive time was due to how PHPExcel names its class names and corresponding file structure. It has an autoloader that converts class names such as *PHPExcel_abc_def* into PHPExcel/abc/def.php for the require statement. Although we had PHPExcel's class directory defined in our include path, our own (already defined) autoloader couldn't handle the manipulation from class name to file name required (it was looking for *PHPExcel_abc_def.php*). When a class file cannot be included, our autoloader will loop 5 times with a 10 second delay to see if the file is being updated and so might become available. So for every PHPExcel class that needed to be loaded we were introducing a delay of 50 seconds before hitting PHPExcel's own autoloader which required the file in fine.
Now that I've got that resolved PHPExcel is proving to be truly awesome.

I'm using the latest version of PHPExcel (1.8.1) in a Symfony project, and I also ran into time delays when using the $objReader->load($file) method. The time delays were not due to an autoloader, but to the load method itself. This method actually reads every cell in every worksheet. And since my data worksheet was 30 columns wide by 5000 rows, it took about 90 seconds to read all this information on my ancient work computer.
I assumed that the real loading/reading of cell values would occur on the fly as I requested them, but it looks like short of a pretty major re-write of the PHPExcel code, there's no real way around this initial load time delay.

If you know your file is a pretty plain excel file, you can do manual reading. A .xslx file is just a zip archive with the spreadsheet values and structure stored into xml files. This script took me from the 60 seconds used on PHPExcel down to 0.18 seconds.
$zip = new ZipArchive();
$zip->open('path_to/file.xlsx');
$sheet_xml = simplexml_load_string($zip->getFromName('xl/worksheets/sheet1.xml'));
$sheet_array = json_decode(json_encode($xml), true);
$values = simplexml_load_string($zip->getFromName('xl/sharedStrings.xml'));
$values_array = json_decode(json_encode($values), true);
$end_result = array();
if ($sheet_array['sheetData']) {
foreach ($sheet_array['sheetData']['row'] as $r => $row) {
$end_result[$r] = array();
foreach ($row['c'] as $c => $cell) {
if (isset($cell['#attributes']['t'])) {
if ($cell['#attributes']['t'] == 's') {
$end_result[$r][] = $values_array['si'][$cell['v']]['t'];
} else if ($cell['#attributes']['t'] == 'e') {
$end_result[$r][] = '';
}
} else {
$end_result[$r][] = $cell['v'];
}
}
}
}
Result:
Array
(
[0] => Array
(
[0] => A1
[1] => B1
[2] => C1
)
[1] => Array
(
[0] => A2
[1] => B2
[2] => C2
)
)
This is error prone and not optimized, but it works and illustrates the basic idea. If you know your file, then you can make reading very fast. If you allow users to input the files, then you should maybe avoid it - or at least do the neccessary checks.

Related

phpspreadsheet write 10.000 record is too slow

i have a requirement to make report using XLSX file , this report may contains 10.000-1.000.000 rows of trx. i made decision using phpspreadsheet from https://phpspreadsheet.readthedocs.io/en/latest/
The problem is it takes too long to write 10.000 hours of which each record consist of 50 columns. its nearly 24 hours and the script still in running and the progress is 2300/10000, here my codes :
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
$client = new \Redis();
$client->connect('192.168.7.147', 6379);
$pool = new \Cache\Adapter\Redis\RedisCachePool($client);
$simpleCache = new \Cache\Bridge\SimpleCache\SimpleCacheBridge($pool);
\PhpOffice\PhpSpreadsheet\Settings::setCache($simpleCache);
$process_time = microtime(true);
if(!file_exists('test.xlsx')) {
$spreadsheet = new Spreadsheet();
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save("test.xlsx");
unset($writer);
}
for($r=1;$r<=10000;$r++) {
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$spreadsheet = $reader->load("test.xlsx");
$rowArray=[];
for($c=1;$c<=50;$c++) {
$rowArray[]=$r.".Content ".$c;
}
$spreadsheet->getActiveSheet()->fromArray(
$rowArray,
NULL,
'A'.$r
);
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save("test.xlsx");
unset($reader);
unset($writer);
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
}
$process_time = microtime(true) - $process_time;
echo $process_time."\n";
notes :
i propose CSV file but the clients only wants XLSX
without redis cache it gives memory error even only <400 record
im not intended to read .XLSX using php, only write action. looks like the library reads the entire spreadsheet
at example above it takes open-close file every 1 record file, when im doing open->writeall->close its shows memory error at the mid progress
at example above it takes open-close file every 1 record file, when im
doing open->writeall->close its shows memory error at the mid progress
I see you are opening (createReader) and saving (createWriter) each time when filling content in the loop. It may be the cause of slowing down the process. From your logic, eventually you are writing back the content to the same file, so it can just be open-one-time > write 50x10k records > close-and-save.
A quick test with re-arrange your coding as follows, which result in approximately 25 seconds using my local Xampp in Windows. I'm not sure if this meets your requirement or not, but I think it may consume more time if the content is some long string. My guess is that if you run on a powerful server, the performance might get significant improve in time wise.
$process_time = microtime(true);
$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$spreadsheet = $reader->load($file_loc);
$row_count = 10000;
$col_count = 50;
for ($r = 1; $r <= $row_count; $r++) {
$rowArray = [];
for ($c = 1; $c <= $col_count; $c++) {
$rowArray[] = $r . ".Content " . $c;
}
$spreadsheet->getActiveSheet()->fromArray(
$rowArray,
NULL,
'A' . $r
);
}
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save($target_dir . 'result_' . $file_name);
unset($reader);
unset($writer);
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
$process_time = microtime(true) - $process_time;
echo $process_time."\n";
Edited:
without redis cache it gives memory error even only <400 record
My quick test is without any cache settings. My guess for the memory issue is that you are opening the XLSX file every time you write the content for each row and then saving it back to the original file.
Every time you open the XLSX file, memory will be loaded and cached with all PhpSpreadsheet object info as well as the [previous content + (50 new columns added each time after saving)] and the memory grows in an exponential way; can you imagine that?
Finally, the memory clearing is way slower and results in memory errors.
1st time open and save
-> open: none
-> save: row A, 50 cols
2nd time open and save
-> open: row A, 50 cols
-> save: row A, 50 cols, row B, 50 cols
3nd time open and save
-> open: row A, 50 cols, row B, 50 cols
-> save: row A, 50 cols, row B, 50 cols, row C, 50 cols
so on and so forth...
memory might still keeping your previous loaded cache
and not releasing so fast (no idea how server is handling the memory, Orz)
and finally memory explode ~ oh no

Generate .xlsx file using fromArray for a big amount of data

I need to write in a .xlsx file about 111.100 rows, using fromArray() but I have a strange error
I use phpspreadsheet library
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$columnLetter = 'A';
foreach ($columnNames as $columnName) {
// Allow to access AA column if needed and more
$sheet->setCellValue($columnLetter.'1', $columnName);
$columnLetter++;
}
$i = 2; // Beginning row for active sheet
$columnLetter = 'A';
foreach ($columnValues as $columnValue) {
$sheet->fromArray(array_values($columnValue), NULL, $columnLetter.$i);
$i++;
$columnLetter++;
}
// Create your Office 2007 Excel (XLSX Format)
$writer = new Xlsx($spreadsheet);
// In this case, we want to write the file in the public directory
// e.g /var/www/project/public/my_first_excel_symfony4.xlsx
$excelFilepath = $directory . '/'.$filename.'.xlsx';
// Create the file
$writer->save($excelFilepath);
And I get the exception :
message: "Invalid cell coordinate AAAA18272"
#code: 0
#file: "./vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Coordinate.php"
Can you help me please ?
Excel pages are limited. The limit is huge but still limited. This is a correct filter so you can't write if there is no space for it.
Anyway you shouldnt use excel pages for such a big amount of data, you can try fragmenting it into smaller pieces, but databases should be the way to manipulate such amount of information

Memory while reading large Excel 2007 (.xlsx)

I'm using PHPExcel which I've used before many times. The problem I have now is when reading Excel2007 files (.xlsx - format). What I'm doing is simply looping the the .xlsx file and creating an array by row/column and then print_r()-ing the results after the read operation to make sure the data output is good before importing it into a MySQL database.
Now when reading the Excel2007 .xlsx file (6MB) the output fails, but whats interesting is if I save the file as the older format .xls (1992-2004 - Excel5) the file becomes larger (16MB) but outputs correctly. This made me think originally it wasn't a memory problem since the older larger .xls file (16MB) ran with no problems and was almost 3x the size of the .xlsx file (6MB).
For test purposes I then copied 25 rows of the 30,000 in the .xlsx (6MB) file and created a new Excel2007 .xlsx and ran the import against the smaller 25 row data-set and it output correctly. This then led me think that it is a memory problem however related specifically to the .xlsx format...
I'm running the server on Amazon Web Services and have C4.Xlarge (16-core, 30GB RAM) so I should have plenty of resources to run this operation.
Question: Why does my output fail when reading a smaller .xlsx file vs a larger .xls file, but then succeed with a smaller .xlsx (25-row) file?
//PHP Function
function parse_xls($file){
ini_set('memory_limit','-1');
$type = PHPExcel_IOFactory::identify($file);
$reader = PHPExcel_IOFactory::createReader($type);
$reader->setReadDataOnly(true);
$xls = $reader->load($file);
$sheet = $xls->getActiveSheet();
$highestRow = $sheet->getHighestRow();
$highestColumn = $sheet->getHighestColumn();
$highestColumnIndex = PHPExcel_Cell::columnIndexFromString($highestColumn);
for($row=2; $row <= ($highestRow+2); $row++){
$import[$row] = [];
for($col=0; $col < $highestColumnIndex; $col++){
$result = $sheet->getCellByColumnAndRow($col, $row)->getValue();
array_push($import[$row],$result);
}
}
print_r($import);
die();
}
for big files i use chunkReadFilter
$iChunkSize=1000;
for($iStartRow = $row_start; $iStartRow <= $totalRows; $iStartRow += $iChunkSize) {
$objReader = $oExcel->SetCreateReader();
$oChunkFilter = new chunkReadFilter();
$objReader->setReadFilter($oChunkFilter);
$oChunkFilter->setRows($iStartRow,$iChunkSize);
$objReader->setReadFilter($oChunkFilter);
$objReader->setReadDataOnly(true);
$objPHPExcel = $objReader->load($files['path']);
$objPHPExcel->setActiveSheetIndex($iList);
$sFromCell = 'A'.$iStartRow;
$aData = $objPHPExcel->getActiveSheet()->toArray(null,true,true,false,$sFromCell);
// free memory
unset($objPHPExcel);
unset($objReader);
unset($oChunkFilter);
// parse data
foreach ($aData as $sKey => $aValue) {
...
}
// real data rows
if (count($aData) < $iChunkSize) {
unset($aData);
break;
}
unset($aData);
}

Uploading data from Excel to DB in PHP making size issues

I have to upload excel and read data then writing to DB. I need to get data from 2mb excel file. I have done coding up to reading excel data. While uploading excel there was shown some issues
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20 bytes)
I have solved this by using
ini_set('memory_limit', '-1');
and also execution time issues that also somehow managed by
set_time_limit(0);
the problem is when I'm trying to upload excel size more than 300kb, it taking too much to complete the execution. Can anyone suggest me best way to read data from excel ? Is there any performance issue if I'm storing the values to an array before inserting to DB ?
Adding my codes here
<?php
/** Include path **/
set_include_path(get_include_path() . PATH_SEPARATOR . 'Classes/');
/** PHPExcel_IOFactory */
include 'PHPExcel/IOFactory.php';
include('../includes/class_read_xl.php');
$obj_read_xl=new class_read_xl();
if(isset($_POST["upload"])) {
move_uploaded_file($_FILES["ufile"]["tmp_name"], "../uploads/xlsheet/" . $_FILES["ufile"]["name"]);
$file = $_FILES['ufile']['name'];
$inputFileName = '../uploads/xlsheet/'.$file;
//$inputFileName = "test.xls";
$inputFileType = PHPExcel_IOFactory::identify($inputFileName);
$objReader = PHPExcel_IOFactory::createReader($inputFileType);
$objReader->setReadDataOnly(false);
// Load $inputFileName to a PHPExcel Object
$objPHPExcel = $objReader->load($inputFileName);
$total_sheets=$objPHPExcel->getSheetCount(); // here 4
$allSheetName=$objPHPExcel->getSheetNames(); // array ([0]=>'student',[1]=>'teacher',[2]=>'school',[3]=>'college')
//print_r($allSheetName);
$objWorksheet = $objPHPExcel->setActiveSheetIndex(0); // first sheet
$highestRow = $objWorksheet->getHighestRow(); // here 5
$highestColumn = $objWorksheet->getHighestColumn(); // here 'E'
$highestColumnIndex = PHPExcel_Cell::columnIndexFromString($highestColumn); // here 5
//exit();
$arr_data=array();
for ($row = 1; $row <= $highestRow; ++$row) {
for ($col = 0; $col <= $highestColumnIndex; ++$col) {
$value = $objWorksheet->getCellByColumnAndRow($col, $row)->getValue();
if(is_array($arr_data) ) {
$arr_data[$row-1][$col]=$value;
}
}
}
print_r($arr_data);
}
?>
Doesn't look as though you're using any of the recommended memory saving methods such as cell caching or "chunking", which are described in the PHPExcel documentation - see section 4.2.1 of the developer documentation (entitled "Cell Caching", and various sections of the User documentation for Reading Spreadsheet files.
If you're only actually reading data from a single worksheet in the file, just load that one worksheet, not all four
Storing values to an array will significantly increase the memory requirements of the script, because your array will also take up memory. However, rather than reading every cell in a row individually, you could use the rangeToArray() method for each row, which wouldn't be such a big memory overhead, and is faster than reading each individual cell

PHPExcel - Existing array functions get converted into normal functions?

Greetings all,
I'm trying to write a script that loads an existing spreadsheet containing a number of array formulas, add data to a worksheet and save it. When opening the file after the script runs, the spreadsheet's formulas are no longer array formulas.
Below is the stripped down version of what I'm attempting:
$excelFile = new PHPExcel();
$fileName = 'blah.xlsx';
$excelReader = PHPExcel_IOFactory::createReader('Excel2007');
$excelFile = $excelReader->load($fileName);
//first sheet contains formulas to process the resulting dump
$excelFile->setActiveSheetIndex(1);
// just to illustrate what's used when retrieving data
...
while($record = db_fetch_object($queryResult)) {
$excelFile->getActiveSheet()->setCellValueByColumnAndRow($col, $row, $record->field);
}
$excelWriter = PHPExcel_IOFactory::createWriter($excelFile, 'Excel2007');
$excelWriter->save($fileName);
After the script runs, a formula that once appeared as:
{=SUM(A1:C6)}
Now appears as:
=SUM(A1:C6)
Thanks in advance for your insight and input
Tony
It seems that the PHPExcel Cell object does not handle a formula element's attributes, so things like "t=array" would be lost by the time you get to createWriter.
To resolve this issue, we've made modifications to the cell and excel2007 reader and writer classes.
In cell.php:
private $_formulaAttributes;
// getter and setter functions
In reader/excel2007.php:
line 769 - after $this->castToFormula...
if(isset($c->f['t'])){
$attributes = array();
$attributes = $c->f;
$docSheet->getCell($r)->setFormulaAttributes($attributes);
}
In writer/excel2007/worksheet.php:
line 1042 - after case 'f':
$attributes = $pCell->getFormulaAttributes();
if($attributes['t'] == 'array') {
$objWriter->startElement('f');
$objWriter->writeAttribute('t', 'array');
$objWriter->writeAttribute('ref', $pCell->getCoordinate());
$objWriter->writeAttribute('aca', '1');
$objWriter->writeAttribute('ca', '1');
$objWriter->text(substr($pCell->getValue(), 1));
$objWriter->endElement();
} else {
$objWriter->writeElement('f', substr($pCell->getValue(), 1));
}
hope this helps someone...
Unfortunately, the PHPExcel readers and writers don't yet support array formulas. I believed that the Excel2007 reader/writer did, but your experience suggests otherwise.

Categories