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