Not saving excel with PHPSpreadsheet in symfony 4 - php

I'm using "phpoffice/phpspreadsheet": "^1.13", can not save updates into uploaded file. I can read data from it but can not save. The other thing to mention I am running it in the Process in background.
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
//read from file
$reader = new Xlsx();
$reader->setLoadSheetsOnly(["Gifts"]);
$spreadsheet = $reader->load($file);
$worksheet = $spreadsheet->getActiveSheet();
$highestRow = $worksheet->getHighestRow();
for ($row = 5; $row < $highestRow; $row++) {
$results[] = [
'id' => $worksheet->getCell('K'.$row)->getValue(),
];
}
//write to file
$spreadsheet = IOFactory::load($file);
$sheet = $spreadsheet->getSheetByName('Gifts');
$sheet->setCellValue('A1', 'Import Status');
$writer = new XlsxWriter($spreadsheet);
ob_end_clean();
$writer->save($file);
If not use ob_end_clean(); the file is saving corrupt and LibbreOffice cannot open it with ob_end_clean(); the file is opening but without changes.
The purpose is to create entities in db reading data from Excel, but because the file can be large I want to run it in the background, maybe it is not the best approach but the best I could do for now so the process is as follows:
upload file
start new process
Process::fromShellCommandline('php /var/www/app/bin/console app:import:excel "'.$file.'"')->start();
redirect user to another page
send an email with a report of imported stuff
All the steps are working fine except saving Excel, when I am opening the uploaded file it does not have any changes, in other question I have found advice like to add ob_end_clean(); or die() it helped at least to open the file without ob_end_clean(); it shows that it is broken.

Related

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");

Getting Garbled Text while using PHPExcel

I am trying to generate an .ods document using PHP Excel.
Below is the code i am using :
public function init() {
$this->objPHPExcel = new PHPExcel();
}
public function indexAction(){
$iexpertsMapper = new Application_Model_IExpertsMapper();
$response = $iexpertsMapper->getAllIExpertsPaymentDues();
$this->objPHPExcel->getProperties()->setCreator("D")
->setLastModifiedBy("D")
->setTitle("D")
->setSubject("D")
->setDescription("D")
->setKeywords("D")
->setCategory("D");
// Headers
$this->objPHPExcel->setActiveSheetIndex(0)
->setCellValue('A1', 'H')
->setCellValue('B1', strtotime("today"))
->setCellValue('C1', 'CTPL');
// Redirect output to a client’s web browser (OpenDocument)
header('Content-Type: application/vnd.oasis.opendocument.spreadsheet');
header('Content-Disposition: attachment;filename=bank.ods');
$objWriter = PHPExcel_IOFactory::createWriter($this->objPHPExcel, 'OpenDocument');
ob_end_clean();
$objWriter->save('php://output');
exit;
}
Whenever i hit the code from the browser, an ods document is downloaded with gibberish text. I tried various things including adding ob_end_clean(); in my code. But nothing seems to work. Following text appears in my ODS document : �QĿi!��K�y3�J<���Z1�0?Y�L%zV.
This is what i see when i open the document :

PHPExcel library hangs with relative "big" files

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');

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');
}

How to edit a particular line of the csv file using PHP?

I have a PHP script that allow users to upload their data. The first line of the csv file are the headers (fname, lname, age, address, email).
My plan is - after the users uploaded their csv, my script will run a function to check the spelling of the headers. If there are misspelled header, my script will correct it. I am using the code below to correct the headers:
if (($file = fopen($csvFile , "r")) != FALSE) {
$ctr = 0;
$record = fgetcsv($file, 1024)) != FALSE) {
if ($ctr == 0) {
correctHeader($record);
# write to new csv.
} else {
# write to new csv.
}
}
}
After correcting, the value of the header and the succeeding lines will be appended on the new csv file. I think this step can be optimized, if I could just edit the first line of the csv (header) and skip the # write to new csv step.
One of the ways I can think of is the following:
Use fgets() to get the first line of the file (instead of fgetcsv()).
Save the length of the line in bytes.
Parse the line with str_getcsv().
Correct headers as needed.
Save headers into a new CSV file.
fopen() the original CSV file for reading.
fseek() the original CSV file handle to length of first line (saved in step 2) + 1.
fopen() the new CSV file for writing (appending actually).
fread() the original CSV file in the loop until EOF and fwrite() chunks into the new CSV file.
Fix bugs.
Have a pint. :)
Here's the code (minus the loop for reading):
<?php
$from = 'd.csv';
$to = 'd.good.csv';
$old = fopen($from, 'r');
if (!is_resource($old)) {
die("Failed to read from source file: $from");
}
$headerLine = fgets($old);
$headerLine = fixHeaders($headerLine);
$new = fopen($to, 'w');
if (!is_resource($new)) {
die("Failed to write to destination file: $new");
}
// Save the fixed header into the new file
fputs($new, $headerLine);
// Read the rest of old and save to new.
// Old file is already open and we are the second line.
// For large files, reading should probably be done in the loop with chunks.
fwrite($new, fread($old, filesize($from)));
// Close files
fclose($old);
fclose($new);
// Just an example
function fixHeaders($line) {
return strtoupper($line);
}

Categories