Hi all :) I'm creating an excel with PhpSpreadsheet (no prob with that ). I'm working with MVC pattern so flow is :
1) User press download button and call function in controller
2) function in controller call a function in an helpers to create the excel
Now it's a very very long task and i'd like to show the progress of this long task (like a download bar). I've tried with all things that i know (ajax polling , iframe, sse, session variable, static variable) but none seems to work. Could you help me ?
@dack94 I did exactly what you are talking about a long time ago, and it's a surprisingly trick thing to do. I would try to avoid it actually. One serious limitation with normal ajax thing is the browser limitation of 2 connection to a given server. So in some condition, your ajax polling might not actually start before the excel file is done generating. Anyway here is my solution that you will have to adapt.
Utility Class:
<?php
namespace Application\Utility;
/**
* This class implements ways to know when a long process is running on
* the server and when the said process is complete.
*
* This file *MUST NOT* be part of Zend code because it has to be accessible concurrently
* to any process that may occur at the same time by another HTTP request. Hence we
* cannot use session and cannot use ACL security system neither. It must stay totally
* separated from all other code.
*/
abstract class Progress
{
private static $currentIdProgress = '';
/**
* Returns the path for the path for the lock file for the specified ID of progress
*
* @param string $id
*
* @return string
*/
private static function getLockFilePath(string $id): string
{
// Here we use MD5 to prevent security hole to access filesystem with '.' or '/' characters in $id
return __DIR__ . '/data/tmp/progress_' . md5($id) . '.lock';
}
/**
* Notify the beginning of a long process
*
* @param string $id
*/
public static function start(string $id): void
{
self::$currentIdProgress = $id;
touch(self::getLockFilePath($id));
}
/**
* Notify the end of a long process
*/
public static function end(): void
{
$path = self::getLockFilePath(self::$currentIdProgress);
if (file_exists($path)) {
unlink($path);
}
}
/**
* Allow to poll the status of a progress by its ID
*
* @param string $id id of progress
*
* @return string current status of progress
*/
public static function status(string $id): string
{
if ($id && file_exists(self::getLockFilePath($id))) {
return $id . ' in progress ...';
}
return '';
}
}
Then your javascript would start the download with an arbitrary "process id". Any string generated in JS. When starting to generate the excel file and you receive that id in PHP, you would call:
Progress::start($_REQUEST['id']);
$spreadsheet = createMySpreadsheet();
Progress::end();
sendSpreadsheetToBrowser($spreadsheet);
In parallel the JS would start to poll a different endpoint. That endpoint MUST be completely separate from your application and it MUST NOT use PHP session at all. In that new endpoint you use:
<?php
/**
* This file *MUST NOT* be part of Zend code because it has to be accessible concurrently
* to any process that may occur at the same time by another HTTP request. Hence we
* cannot use session and cannot use ACL security system neither. It must stay totally
* separated from all other code.
*/
require_once __DIR__ . '/../vendor/autoload.php';
echo \Application\Utility\Progress::status($_REQUEST['id']);
The JS can then show an non-deterministric progress bar, until the endpoint says it's over
<?php
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', 'Hello World !');
$writer = new Xlsx($spreadsheet);
$writer->save('hello world.xlsx');
Error is :
[InvalidArgumentException]
Package phpoffice/phpspreadsheet at version has a PHP requirement incompat ible with your PHP version (5.4.16)
Any idea how to fix?
<?php
include_once 'include/bootstrap.php'; // Adjust bootstrap to your environment.
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
print "[".date("Y-m-d H:i:s",time())."] Start Data Population.\n";
$spreadsheet = new Spreadsheet();
$spreadsheet->setActiveSheetIndex(0);
$columnLookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // Limit to 26 columns
//$numberOfRows = 2500; // Takes Seconds
//$numberOfRows = 25000; // Take Minutes
$numberOfRows = 50000; // Take Minutes
//$numberOfRows = 250000; // Takes Hours
for ($row = 1; $row <= $numberOfRows; $row++) {
for ($column = 1; $column <= 26; $column++) {
$columnChar = substr($columnLookup, ($column -1), 1);
$spreadsheet->getActiveSheet()
->getCell("{$columnChar}{$row}")
->setValueExplicit(
"{$columnChar}{$row}",
\PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING
);
}
if ($row % 1000 == 0) print "."; // show progress once in awhile..
}
print "\n";
print "[".date("Y-m-d H:i:s",time())."] End Data Population.\n";
print "[".date("Y-m-d H:i:s",time())."] Start File Creation.\n";
$writer = new Xlsx($spreadsheet);
$writer->setPreCalculateFormulas(false); // Has no affect given the spreadsheet has no formulas!
$writer->save('/tmp/xlsxExample.xlsx'); // This takes --forever-- ..but why?
print "[".date("Y-m-d H:i:s",time())."] End File Creation.\n";
.xls
file, which is formatted as HTML plain text. When I read the file using the \PhpOffice\PhpSpreadsheet\Reader\Html
reader and then save the file as "Microsoft Excel 97-2003 (.xls)" format and use the \PhpOffice\PhpSpreadsheet\Reader\Xls
the rows in the array from the worksheet are different. Is there any reason for this?
Hello! Wasn't sure where to post, but I wanted to raise the point that lack of support for MS excel "format as table" creates accessibility issues. "Format as table" is the most widely supported technique for screen readers to properly associate column headers.
Here's a link to the official guide for making accessible Excel files (for meeting US laws):
https://www.section508.gov/create/spreadsheets
It seems like "format as table" is realized using pivot tables...
Related issues on github: #972 #840 #358
Related questions on stackoverflow:
https://stackoverflow.com/questions/55907831/phpspreadsheet-tables-get-lost-in-the-process
https://stackoverflow.com/questions/50934401/phpspreadsheet-multiple-tables-with-format-as-table-in-excel-worksheet