diff --git a/Service/AbstractWriter.php b/Service/AbstractWriter.php index 9d264f5..7742920 100644 --- a/Service/AbstractWriter.php +++ b/Service/AbstractWriter.php @@ -9,11 +9,25 @@ abstract class AbstractWriter { protected $logger; + /** + * Absolute path to the file that will be written by the writer + * + * @var string + */ + protected $filepath; + public function __construct(LoggerInterface $logger) { $this->logger = $logger; } + /** + * Initialize the writer + * + * @param string $filepath Path to the file that needs to be created by the writer + */ + abstract public function setup($filepath); + /** * Write the formatted order data to disk, so we can fetch it later * @@ -83,4 +97,10 @@ private function findNestedHeader($key, $existingHeaders) } abstract public function prepare($cacheFile, $sortedHeaders); -} \ No newline at end of file + + abstract public function writeHeaders($headers, $initRow = null); + + abstract public function writeRow($row, $headers = []); + + abstract public function finalize(); +} diff --git a/Service/CSVWriter.php b/Service/CSVWriter.php index 07d7485..4fe1abd 100644 --- a/Service/CSVWriter.php +++ b/Service/CSVWriter.php @@ -4,6 +4,18 @@ class CSVWriter extends AbstractWriter { + /** + * A file system pointer resource that is typically created using fopen() + * + * @var resource + */ + private $handle; + + public function setup($filepath) + { + $this->handle = fopen($filepath, 'w+'); + } + /** * Write the compiled csv to disk and return the file name * @@ -14,7 +26,7 @@ class CSVWriter extends AbstractWriter public function prepare($cacheFile, $sortedHeaders) { $csvFile = $cacheFile . '.csv'; - $handle = fopen($csvFile, 'w'); + $this->setup($csvFile); // Generate a csv version of the multi-row headers to write to disk $headerRows = [[], []]; @@ -39,8 +51,8 @@ public function prepare($cacheFile, $sortedHeaders) } } - fputcsv($handle, $headerRows[0]); - fputcsv($handle, $headerRows[1]); + $this->writeRow($headerRows[0]); + $this->writeRow($headerRows[1]); // TODO: Track memory usage $file = new \SplFileObject($cacheFile); @@ -65,15 +77,39 @@ public function prepare($cacheFile, $sortedHeaders) } } - fputcsv($handle, $csvRow); + $this->writeRow($csvRow); $file->next(); } $file = null; // Get rid of the file handle that SplFileObject has on cache file unlink($cacheFile); - fclose($handle); + fclose($this->handle); return $csvFile; } -} \ No newline at end of file + + public function writeHeaders($headers, $initRow = null) + { + $contents = stream_get_contents($this->handle, -1, 0); + rewind($this->handle); + $this->writeRow($headers); + fwrite($this->handle, $contents); + } + + /** + * Write a row to the csv + * + * @param array $row + * @param array $headers + */ + public function writeRow($row, $headers = []) + { + fputcsv($this->handle, $row); + } + + public function finalize() + { + fclose($this->handle); + } +} diff --git a/Service/ExcelWriter.php b/Service/ExcelWriter.php index 778a796..b099eeb 100644 --- a/Service/ExcelWriter.php +++ b/Service/ExcelWriter.php @@ -23,13 +23,22 @@ public function __construct(LoggerInterface $logger, PHPExcelFactory $phpExcel) /** * Initialize the excel writer * - * @param string $author The author/creator of this file - * @param string $title The title of this file + * @param string $filepath */ - public function setup($author = '', $title = '') + public function setup($filepath) { $this->handle->getActiveSheet()->getPageSetup()->setOrientation(\PHPExcel_Worksheet_PageSetup::ORIENTATION_LANDSCAPE); + $this->filepath = $filepath; + } + /** + * Set meta properties for the excel writer + * + * @param string $author The author/creator of this file + * @param string $title The title of this file + */ + public function setProperties($author = '', $title = '') + { $this->handle->getProperties() ->setCreator($author) ->setTitle($title); @@ -63,16 +72,23 @@ public function setSheetTitle($title) /** * Write the headers (nested or otherwise) to the current active worksheet * - * @param $sortedHeaders + * @param $headers + * @param $initRow + * * @throws \PHPExcel_Exception */ - public function writeHeaders($sortedHeaders) + public function writeHeaders($headers, $initRow = null) { $worksheet = $this->handle->getActiveSheet(); - $initRow = $this->currentRow; + if ($initRow) { + $worksheet->insertNewRowBefore($initRow, 2); + } else { + $initRow = $this->currentRow; + } + $column = 'A'; - foreach ($sortedHeaders as $idx => $header) { + foreach ($headers as $idx => $header) { $cell = $column . $initRow; if (!is_array($header)) { $worksheet->setCellValue($cell, $header); @@ -140,8 +156,14 @@ public function writeRows($rows, $headers) } } - public function writeRow($dataRow, $headers) + public function writeRow($dataRow, $headers = []) { + if (empty($headers)) { + // No headers to manage. Just write this array of data directly + $this->writeArray($dataRow); + return; + } + if (!is_array($dataRow)) { // Invalid data -- don't process this row return; @@ -163,26 +185,33 @@ public function writeRow($dataRow, $headers) } } - $this->handle->getActiveSheet()->fromArray($excelRow, null, 'A' . $rowIdx); - $this->currentRow++; + $this->writeArrays($excelRow); } /** - * Write ad-hoc set of rows without any dependence on headers + * Write one or more rows starting at the given row and column * * @param array $lines - * @param int $row - * @param string $column */ - public function writeRawRows(array $lines, $column = 'A', $row = null) + private function writeArrays(array $lines) { - if (is_null($row)) { - $row = $this->currentRow; - } - $this->handle->getActiveSheet()->fromArray($lines, null, $column . $row); + $startCell = 'A' . $this->currentRow; + $this->handle->getActiveSheet()->fromArray($lines, null, $startCell); $this->currentRow += count($lines); } + /** + * Write a single row of data + * + * @param array $row A single row of data + */ + private function writeArray(array $row) + { + $startCell = 'A' . $this->currentRow; + $this->handle->getActiveSheet()->fromArray([$row], null, $startCell); + $this->currentRow++; + } + /** * Freeze panes at the given location so they stay fixed upon scroll * @@ -214,17 +243,16 @@ public function addHorizontalPageBreak($cell = '') /** * Save the current data into an .xlsx file * - * @param $filename * @throws \PHPExcel_Exception */ - public function finalize($filename) + public function finalize() { // Set active sheet index to the first sheet, so Excel opens this as the first sheet $this->handle->setActiveSheetIndex(0); // Write the file to disk $writer = $this->phpexcel->createWriter($this->handle, 'Excel2007'); - $writer->save($filename); + $writer->save($this->filepath); } public function resetCurrentRow($pos) @@ -248,4 +276,4 @@ private function incrementColumn($str, $count = 1) return $str; } -} \ No newline at end of file +} diff --git a/Tests/Service/CSVWriterTest.php b/Tests/Service/CSVWriterTest.php new file mode 100644 index 0000000..b53c472 --- /dev/null +++ b/Tests/Service/CSVWriterTest.php @@ -0,0 +1,42 @@ +writer = $this->buildService(); + $this->testfilename = uniqid() . ".csv"; + $this->writer->setup($this->testfilename); + } + + public function tearDown() + { + $this->writer->finalize(); + unlink($this->testfilename); + } + + public function buildService($params = []) + { + $defaults = ['logger' => $this->getMockBuilder('Psr\Log\LoggerInterface')->disableOriginalConstructor()->getMock()]; + $params = array_merge($defaults, $params); + + return new CSVWriter($params['logger']); + } + + public function testShouldWriteRowToFile() + { + $this->writer->writeRow(['a', 'b', 'c,', 'd']); + $this->writer->writeRow(['e', 'f', 'g', 'h']); + + $contents = file_get_contents($this->testfilename); + $expected = "a,b,\"c,\",d\ne,f,g,h\n"; + $this->assertEquals($expected, $contents); + } +} diff --git a/Tests/Service/ExcelWriterTest.php b/Tests/Service/ExcelWriterTest.php index 24eb689..4db2f1b 100644 --- a/Tests/Service/ExcelWriterTest.php +++ b/Tests/Service/ExcelWriterTest.php @@ -26,22 +26,29 @@ public function buildService($params = []) } public function testShouldInitializePHPExcelObject() + { + // Setup all the mocks + $pageSetupMock = $this->getMockBuilder('\PHPExcel_Worksheet_PageSetup')->disableOriginalConstructor()->getMock(); + $pageSetupMock->expects($this->once())->method('setOrientation')->with('landscape'); + $worksheetMock = $this->getMockBuilder('\PHPExcel_Worksheet')->disableOriginalConstructor()->getMock(); + $worksheetMock->expects($this->once())->method('getPageSetup')->willReturn($pageSetupMock); + $this->phpExcelHandleMock->expects($this->once())->method('getActiveSheet')->willReturn($worksheetMock); + + $this->buildService()->setup("filename.xlsx"); + } + + public function testShouldSetPropertiesForExcelFile() { $author = 'foo'; $title = 'bar'; // Setup all the mocks - $pageSetupMock = $this->getMockBuilder('\PHPExcel_Worksheet_PageSetup')->disableOriginalConstructor()->getMock(); - $pageSetupMock->expects($this->once())->method('setOrientation')->with('landscape'); $propertiesMock = $this->getMockBuilder('\PHPExcel_DocumentProperties')->disableOriginalConstructor()->getMock(); $propertiesMock->expects($this->once())->method('setCreator')->with($author)->willReturn($propertiesMock); $propertiesMock->expects($this->once())->method('setTitle')->with($title)->willReturn($propertiesMock); - $worksheetMock = $this->getMockBuilder('\PHPExcel_Worksheet')->disableOriginalConstructor()->getMock(); - $worksheetMock->expects($this->once())->method('getPageSetup')->willReturn($pageSetupMock); - $this->phpExcelHandleMock->expects($this->once())->method('getActiveSheet')->willReturn($worksheetMock); $this->phpExcelHandleMock->expects($this->once())->method('getProperties')->willReturn($propertiesMock); - $this->buildService()->setup($author, $title); + $this->buildService()->setProperties($author, $title); } public function testShouldSetCurrentWorksheet() @@ -151,24 +158,14 @@ public function testShouldWriteNestedRowData() $this->buildService()->writeRow($input, $headers); } - public function testShouldWriteRawDataStartingFromTheFirstCell() - { - $lines = ['Lorem', 'Ipsum', 'Dolor', 'Sit', 'Amet']; - $worksheetMock = $this->getMockBuilder('\PHPExcel_Worksheet')->disableOriginalConstructor()->getMock(); - $worksheetMock->expects($this->once())->method('fromArray')->with($lines, null, 'A1'); - $this->phpExcelHandleMock->expects($this->once())->method('getActiveSheet')->willReturn($worksheetMock); - - $this->buildService()->writeRawRows($lines); - } - - public function testShouldWriteRawDataStartingFromTheGivenCell() + public function testShouldWriteArrayAsDataStartingFromTheFirstCell() { $lines = ['Lorem', 'Ipsum', 'Dolor', 'Sit', 'Amet']; $worksheetMock = $this->getMockBuilder('\PHPExcel_Worksheet')->disableOriginalConstructor()->getMock(); - $worksheetMock->expects($this->once())->method('fromArray')->with($lines, null, 'C3'); + $worksheetMock->expects($this->once())->method('fromArray')->with([$lines], null, 'A1'); $this->phpExcelHandleMock->expects($this->once())->method('getActiveSheet')->willReturn($worksheetMock); - $this->buildService()->writeRawRows($lines, 'C', 3); + $this->buildService()->writeRow($lines); } public function testShouldFreezePanes() @@ -210,14 +207,21 @@ public function testShouldSaveFileInExcel2007Format() $writerMock = $this->getMockBuilder('\PHPExcel_Writer_IWriter')->disableOriginalConstructor()->getMock(); $writerMock->expects($this->once())->method('save')->with($filename); + // Setup all the mocks + $pageSetupMock = $this->getMockBuilder('\PHPExcel_Worksheet_PageSetup')->disableOriginalConstructor()->getMock(); + $worksheetMock = $this->getMockBuilder('\PHPExcel_Worksheet')->disableOriginalConstructor()->getMock(); + $worksheetMock->expects($this->once())->method('getPageSetup')->willReturn($pageSetupMock); + $this->phpExcelHandleMock->expects($this->once())->method('getActiveSheet')->willReturn($worksheetMock); + $phpExcel = $this->getPHPExcelMock(); $phpExcel->expects($this->once())->method('createWriter') ->with($this->phpExcelHandleMock, 'Excel2007') ->willReturn($writerMock); $service = $this->buildService(['phpExcel' => $phpExcel]); + $service->setup($filename); - $service->finalize($filename); + $service->finalize(); } private function getPHPExcelMock() @@ -227,4 +231,4 @@ private function getPHPExcelMock() return $phpExcel; } -} \ No newline at end of file +}