diff --git a/.gitignore b/.gitignore index 3a9875b..a75dba1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ composer.lock +.idea diff --git a/composer.json b/composer.json index 72e84c0..391f84d 100644 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "require": { "ext-json": "*", "php": ">=7.1", - "symfony/framework-bundle": ">=2.1", + "symfony/framework-bundle": "4.4.*", "psr/log": "~1.0", "phpoffice/phpspreadsheet": "^1.3" }, diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 246e7af..7a111e0 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -11,7 +11,7 @@ services: - { name: monolog.logger, channel: csvwriter } excelwriter: - class: %excelwriter.service.class% + class: "%excelwriter.service.class%" arguments: [ "@logger", "@phpexcel" ] tags: - { name: monolog.logger, channel: excelwriter } diff --git a/src/Service/ExcelWriter.php b/src/Service/ExcelWriter.php index 29ba511..61fbd35 100644 --- a/src/Service/ExcelWriter.php +++ b/src/Service/ExcelWriter.php @@ -87,10 +87,11 @@ public function setSheetTitle($title) * * @param $headers * @param $initRow + * @param $flattenHeaders * * @throws \PhpOffice\PhpSpreadsheet\Exception */ - public function writeHeaders($headers, $initRow = null) + public function writeHeaders($headers, $initRow = null, $flattenHeaders = false) { $worksheet = $this->spreadsheet->getActiveSheet(); $hasMultiRowHeaders = $this->hasMultiRowHeaders($headers); @@ -107,41 +108,56 @@ public function writeHeaders($headers, $initRow = null) if (!is_array($header)) { $worksheet->setCellValue($cell, $header); $worksheet->getColumnDimension($column)->setAutoSize(true); - if ($hasMultiRowHeaders) { + if ($hasMultiRowHeaders && !$flattenHeaders) { // These set of headers contain multi-row headers. So this cell needs to be merged with cell in the // row below it. $worksheet->mergeCells($column . $initRow . ':' . $column . ($initRow + 1)); } + // Mark headers as bold + $worksheet->getStyle($cell)->getFont()->setBold(true); $column++; } else { - // This is a multi-row header, the first row consists of one value merged across several cells and the - // second row contains the "children". - // Write the first row of the header $arrKeys = array_keys($header); $headerName = reset($arrKeys); - $worksheet->setCellValue($cell, $headerName); - - // Figure out how many cells across to merge - $mergeLength = count($header[$headerName]) - 1; - $mergeDestination = $this->incrementColumn($column, $mergeLength); - $worksheet->mergeCells($column . $initRow . ':' . $mergeDestination . $initRow); - - // Now write the children's values onto the second row - foreach ($header[$headerName] as $subHeaderName) { - $worksheet->setCellValue($column . ($initRow + 1), $subHeaderName); - $worksheet->getColumnDimension($column)->setAutoSize(true); - $column++; + + if ($flattenHeaders) { + // If $flattenHeaders true, we are going to flatten nested headers as single header row + // We only consider "children" for headers ignoring parent header name completely. + + // Now write the children's values as flattened header in the row + foreach ($header[$headerName] as $subHeaderName) { + $cell = $column . $initRow; + $worksheet->setCellValue($cell, $subHeaderName); + $worksheet->getColumnDimension($column)->setAutoSize(true); + // Mark child headers as bold + $worksheet->getStyle($cell)->getFont()->setBold(true); + $column++; + } + } else { + $worksheet->setCellValue($cell, $headerName); + + // Figure out how many cells across to merge + $mergeLength = count($header[$headerName]) - 1; + $mergeDestination = $this->incrementColumn($column, $mergeLength); + $worksheet->mergeCells($column . $initRow . ':' . $mergeDestination . $initRow); + + // Now write the children's values onto the second row + foreach ($header[$headerName] as $subHeaderName) { + $worksheet->setCellValue($column . ($initRow + 1), $subHeaderName); + $worksheet->getColumnDimension($column)->setAutoSize(true); + $column++; + } + + // Mark parent headers as bold + $worksheet->getStyle($cell)->getFont()->setBold(true); } } - - // Mark headers as bold - $worksheet->getStyle($cell)->getFont()->setBold(true); } $worksheet->calculateColumnWidths(); - $this->currentRow = $initRow + (($hasMultiRowHeaders) ? 2 : 1); + $this->currentRow = $initRow + (($hasMultiRowHeaders && !$flattenHeaders) ? 2 : 1); } /** @@ -169,13 +185,15 @@ private function hasMultiRowHeaders($headers) * @param string $cacheFile Filename where the fetched data can be cached from * @param array $sortedHeaders Headers to write sorted in the order you want them * @param bool $freezeHeaders True if you want to freeze headers (default: false) + * @param bool $flattenHeaders True if you want to flatten nested headers (default: false) * @throws \PhpOffice\PhpSpreadsheet\Exception */ - public function prepare($cacheFile, $sortedHeaders, $freezeHeaders = false) + public function prepare($cacheFile, $sortedHeaders, $freezeHeaders = false, $flattenHeaders = false) { - $this->writeHeaders($sortedHeaders); + $this->writeHeaders($sortedHeaders, null, $flattenHeaders); if($freezeHeaders) { - $this->freezePanes(); + //if $flattenHeaders true, freeze the rows above cell A2 (i.e row 1) + $flattenHeaders ? $this->freezePanes('A2') : $this->freezePanes(); } $file = new \SplFileObject($cacheFile); @@ -321,7 +339,7 @@ private function writeArray(array $row) public function freezePanes($cell = '') { if (empty($cell)) { - $cell = 'A3'; + $cell = 'A3'; // A3 will freeze the rows above cell A3 (i.e row 2) } $this->spreadsheet->getActiveSheet()->freezePane($cell); } diff --git a/test/Service/ExcelWriterTest.php b/test/Service/ExcelWriterTest.php index f395b41..9082bec 100644 --- a/test/Service/ExcelWriterTest.php +++ b/test/Service/ExcelWriterTest.php @@ -95,7 +95,7 @@ public function testShouldWriteSingleRowHeaders() $this->buildService()->writeHeaders($headers); } - public function testShouldWriteNestedHeaders() + public function testShouldWriteNestedHeadersIfFlattenHeadersIsNotSet() { $columnDimensionMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension')->disableOriginalConstructor()->getMock(); $columnDimensionMock->expects($this->exactly(6))->method('setAutoSize')->with(true); @@ -123,6 +123,32 @@ public function testShouldWriteNestedHeaders() $this->buildService()->writeHeaders($headers); } + + public function testShouldWriteFlattenedNestedHeadersWithoutParentHeaderIfFlattenHeadersIsTrue() + { + $columnDimensionMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension')->disableOriginalConstructor()->getMock(); + $columnDimensionMock->expects($this->exactly(6))->method('setAutoSize')->with(true); + + $phpExcelStyleMock2 = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Style\Font')->disableOriginalConstructor()->getMock(); + $phpExcelStyleMock2->expects($this->exactly(6))->method('setBold')->with(true); + $phpExcelStyleMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Style\Style')->disableOriginalConstructor()->getMock(); + $phpExcelStyleMock->expects($this->exactly(6))->method('getFont')->willReturn($phpExcelStyleMock2); + + $worksheetMock = $this->getMockBuilder('\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet')->disableOriginalConstructor()->getMock(); + $worksheetMock->expects($this->exactly(6))->method('getStyle')->willReturn($phpExcelStyleMock); + $worksheetMock->expects($this->exactly(6))->method('setCellValue')->withConsecutive( + ['A1', 'Alpha'], ['B1', 'Bravo'], ['C1', 'Gamma'], ['D1', 'Delta'], ['E1', 'Foxtrot'], ['F1', 'Hotel'] + ); + $worksheetMock->expects($this->exactly(0))->method('mergeCells'); + $worksheetMock->expects($this->exactly(6))->method('getColumnDimension')->withConsecutive( + ['A'], ['B'], ['C'], ['D'], ['E'], ['F'] + )->willReturn($columnDimensionMock); + $this->spreadsheet->expects($this->once())->method('getActiveSheet')->willReturn($worksheetMock); + + $headers = [0 => 'Alpha', 1 => 'Bravo', 2 => 'Gamma', 3 => 'Delta', 4 => ['Echo' => ['Foxtrot', 'Hotel']]]; + $this->buildService()->writeHeaders($headers, null, true); + } + public function testShouldWriteNonNestedData() { $input = [