createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName);
/** @var Writer $writer */
$writer = WriterEntityFactory::createWriter(Type::XLSX);
$writer->setShouldUseInlineStrings($shouldUseInlineStrings);
$writer->setShouldCreateNewSheetsAutomatically(true);
$writer->openToFile($resourcePath);
for ($i = 1; $i <= $numRows; $i++) {
$writer->addRow(WriterEntityFactory::createRowFromArray(["xlsx--{$i}-1", "xlsx--{$i}-2", "xlsx--{$i}-3"]));
}
$writer->close();
if ($shouldUseInlineStrings) {
$numSheets = count($writer->getSheets());
$this->assertEquals($numRows, $this->getNumWrittenRowsUsingInlineStrings($resourcePath, $numSheets), "The created XLSX ($fileName) should contain $numRows rows");
} else {
$this->assertEquals($numRows, $this->getNumWrittenRowsUsingSharedStrings($resourcePath), "The created XLSX ($fileName) should contain $numRows rows");
}
$executionTime = time() - $startTime;
$this->assertTrue($executionTime < $expectedMaxExecutionTime, "Writing 1 million rows should take less than $expectedMaxExecutionTime seconds (took $executionTime seconds)");
$memoryPeakUsage = memory_get_peak_usage(true) - $beforeMemoryPeakUsage;
$this->assertTrue($memoryPeakUsage < $expectedMaxMemoryPeakUsage, 'Writing 1 million rows should require less than ' . ($expectedMaxMemoryPeakUsage / 1024 / 1024) . ' MB of memory (required ' . ($memoryPeakUsage / 1024 / 1024) . ' MB)');
}
/**
* @param string $resourcePath
* @param int $numSheets
* @return int
*/
private function getNumWrittenRowsUsingInlineStrings($resourcePath, $numSheets)
{
$pathToLastSheetFile = 'zip://' . $resourcePath . '#xl/worksheets/sheet' . $numSheets . '.xml';
return $this->getLasRowNumberForFile($pathToLastSheetFile);
}
/**
* @param string $resourcePath
* @return int
*/
private function getNumWrittenRowsUsingSharedStrings($resourcePath)
{
$pathToSharedStringsFile = 'zip://' . $resourcePath . '#xl/sharedStrings.xml';
return $this->getLasRowNumberForFile($pathToSharedStringsFile);
}
/**
* @param string $filePath
* @return string
*/
private function getLasRowNumberForFile($filePath)
{
$lastRowNumber = 0;
// to avoid executing the regex of the entire file to get the last row number,
// we only retrieve the last 200 characters of the shared strings file, as the cell value
// contains the row number.
$lastCharactersOfFile = $this->getLastCharactersOfFile($filePath, 200);
// in sharedStrings.xml and sheetN.xml, the cell value will look like this:
// xlsx--[ROW_NUMBER]-[CELL_NUMBER] or xlsx--[ROW_NUMBER]-[CELL_NUMBER]
if (preg_match_all('/xlsx--(\d+)-\d+<\/t>/', $lastCharactersOfFile, $matches)) {
$lastMatch = array_pop($matches);
$lastRowNumber = (int) (array_pop($lastMatch));
}
return $lastRowNumber;
}
/**
* @param string $filePath
* @param int $numCharacters
* @return string
*/
private function getLastCharactersOfFile($filePath, $numCharacters)
{
// since we cannot execute "tail" on a file inside a zip, we need to copy it outside first
$tmpFile = sys_get_temp_dir() . '/getLastCharacters.xml';
copy($filePath, $tmpFile);
// Get the last 200 characters
$lastCharacters = `tail -c $numCharacters $tmpFile`;
// remove the temporary file
unlink($tmpFile);
return $lastCharacters;
}
}