Failed Conditions
Pull Request — develop_3.0 (#594)
by
unknown
04:29
created

WorksheetManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 9.6333
c 0
b 0
f 0
cc 1
nc 1
nop 8
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace Box\Spout\Writer\XLSX\Manager;
4
5
use Box\Spout\Common\Entity\Cell;
6
use Box\Spout\Common\Entity\Row;
7
use Box\Spout\Common\Entity\Style\Style;
8
use Box\Spout\Common\Exception\InvalidArgumentException;
9
use Box\Spout\Common\Exception\IOException;
10
use Box\Spout\Common\Helper\Escaper\XLSX as XLSXEscaper;
11
use Box\Spout\Common\Helper\StringHelper;
12
use Box\Spout\Common\Manager\OptionsManagerInterface;
13
use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
14
use Box\Spout\Writer\Common\Entity\Options;
15
use Box\Spout\Writer\Common\Entity\Worksheet;
16
use Box\Spout\Writer\Common\Helper\CellHelper;
17
use Box\Spout\Writer\Common\Manager\RowManager;
18
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
19
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
20
use Box\Spout\Writer\XLSX\Manager\Style\StyleManager;
21
22
/**
23
 * Class WorksheetManager
24
 * XLSX worksheet manager, providing the interfaces to work with XLSX worksheets.
25
 */
26
class WorksheetManager implements WorksheetManagerInterface
27
{
28
    /**
29
     * Maximum number of characters a cell can contain
30
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-16c69c74-3d6a-4aaf-ba35-e6eb276e8eaa [Excel 2007]
31
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 [Excel 2010]
32
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-ca36e2dc-1f09-4620-b726-67c00b05040f [Excel 2013/2016]
33
     */
34
    const MAX_CHARACTERS_PER_CELL = 32767;
35
36
    const SHEET_XML_FILE_HEADER = <<<'EOD'
37
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
38
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
39
EOD;
40
41
    /** @var bool Whether inline or shared strings should be used */
42
    protected $shouldUseInlineStrings;
43
44
    /** @var RowManager Manages rows */
45
    private $rowManager;
46
47
    /** @var StyleManager Manages styles */
48
    private $styleManager;
49
50
    /** @var StyleMerger Helper to merge styles together */
51
    private $styleMerger;
52
53
    /** @var SharedStringsManager Helper to write shared strings */
54
    private $sharedStringsManager;
55
56
    /** @var XLSXEscaper Strings escaper */
57
    private $stringsEscaper;
58
59
    /** @var StringHelper String helper */
60
    private $stringHelper;
61
62
    /** @var InternalEntityFactory Factory to create entities */
63
    private $entityFactory;
64
65
    /**
66
     * WorksheetManager constructor.
67
     *
68
     * @param OptionsManagerInterface $optionsManager
69
     * @param RowManager $rowManager
70
     * @param StyleManager $styleManager
71
     * @param StyleMerger $styleMerger
72
     * @param SharedStringsManager $sharedStringsManager
73
     * @param XLSXEscaper $stringsEscaper
74
     * @param StringHelper $stringHelper
75
     * @param InternalEntityFactory $entityFactory
76
     */
77 34
    public function __construct(
78
        OptionsManagerInterface $optionsManager,
79
        RowManager $rowManager,
80
        StyleManager $styleManager,
81
        StyleMerger $styleMerger,
82
        SharedStringsManager $sharedStringsManager,
83
        XLSXEscaper $stringsEscaper,
84
        StringHelper $stringHelper,
85
        InternalEntityFactory $entityFactory
86
    ) {
87 34
        $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS);
88 34
        $this->rowManager = $rowManager;
89 34
        $this->styleManager = $styleManager;
90 34
        $this->styleMerger = $styleMerger;
91 34
        $this->sharedStringsManager = $sharedStringsManager;
92 34
        $this->stringsEscaper = $stringsEscaper;
93 34
        $this->stringHelper = $stringHelper;
94 34
        $this->entityFactory = $entityFactory;
95 34
    }
96
97
    /**
98
     * @return SharedStringsManager
99
     */
100 30
    public function getSharedStringsManager()
101
    {
102 30
        return $this->sharedStringsManager;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108 27
    public function startSheet(Worksheet $worksheet)
109
    {
110 27
        $sheetFilePointer = fopen($worksheet->getFilePath(), 'w');
111 27
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
112
113 27
        $worksheet->setFilePointer($sheetFilePointer);
114
115 27
        fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
116 27
        fwrite($sheetFilePointer, $worksheet->getDefaultXML());
117 27
        fwrite($sheetFilePointer, $worksheet->getColWidthXML());
118 27
        fwrite($sheetFilePointer, '<sheetData>');
119 27
    }
120
121
    /**
122
     * Checks if the sheet has been sucessfully created. Throws an exception if not.
123
     *
124
     * @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
125
     * @throws IOException If the sheet data file cannot be opened for writing
126
     * @return void
127
     */
128 27
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
129
    {
130 27
        if (!$sheetFilePointer) {
131
            throw new IOException('Unable to open sheet for writing.');
132
        }
133 27
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 27
    public function addRow(Worksheet $worksheet, Row $row)
139
    {
140 27
        if (!$this->rowManager->isEmpty($row)) {
141 27
            $this->addNonEmptyRow($worksheet, $row);
142
        }
143
144 26
        $worksheet->setLastWrittenRowIndex($worksheet->getLastWrittenRowIndex() + 1);
145 26
    }
146
147
    /**
148
     * Adds non empty row to the worksheet.
149
     *
150
     * @param Worksheet $worksheet The worksheet to add the row to
151
     * @param Row $row The row to be written
152
     * @throws IOException If the data cannot be written
153
     * @throws InvalidArgumentException If a cell value's type is not supported
154
     * @return void
155
     */
156 27
    private function addNonEmptyRow(Worksheet $worksheet, Row $row)
157
    {
158 27
        $cellIndex = 0;
159 27
        $rowStyle = $row->getStyle();
160 27
        $rowIndex = $worksheet->getLastWrittenRowIndex() + 1;
161 27
        $numCells = $row->getNumCells();
162
163 27
        $rowXML = '<row r="' . $rowIndex . '" spans="1:' . $numCells . '">';
164
165 27
        foreach ($row->getCells() as $cell) {
166 27
            $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndex, $cellIndex);
167 26
            $cellIndex++;
168
        }
169
170 26
        $rowXML .= '</row>';
171
172 26
        $wasWriteSuccessful = fwrite($worksheet->getFilePointer(), $rowXML);
173 26
        if ($wasWriteSuccessful === false) {
174
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
175
        }
176 26
    }
177
178
    /**
179
     * Applies styles to the given style, merging the cell's style with its row's style
180
     * Then builds and returns xml for the cell.
181
     *
182
     * @param Cell $cell
183
     * @param Style $rowStyle
184
     * @param int $rowIndex
185
     * @param int $cellIndex
186
     * @throws InvalidArgumentException If the given value cannot be processed
187
     * @return string
188
     */
189 27
    private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndex, $cellIndex)
190
    {
191
        // Apply row and extra styles
192 27
        $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
193 27
        $cell->setStyle($mergedCellAndRowStyle);
194 27
        $newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
195
196 27
        $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
197
198 27
        return $this->getCellXML($rowIndex, $cellIndex, $cell, $registeredStyle->getId());
199
    }
200
201
    /**
202
     * Builds and returns xml for a single cell.
203
     *
204
     * @param int $rowIndex
205
     * @param int $cellNumber
206
     * @param Cell $cell
207
     * @param int $styleId
208
     * @throws InvalidArgumentException If the given value cannot be processed
209
     * @return string
210
     */
211 27
    private function getCellXML($rowIndex, $cellNumber, Cell $cell, $styleId)
212
    {
213 27
        $columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber);
214 27
        $cellXML = '<c r="' . $columnIndex . $rowIndex . '"';
215 27
        $cellXML .= ' s="' . $styleId . '"';
216
217 27
        if ($cell->isString()) {
218 26
            $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue());
219 3
        } elseif ($cell->isBoolean()) {
220 1
            $cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
221 3
        } elseif ($cell->isNumeric()) {
222 1
            $cellXML .= '><v>' . $cell->getValue() . '</v></c>';
223 3
        } elseif ($cell->isEmpty()) {
224 1
            if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) {
225
                $cellXML .= '/>';
226
            } else {
227
                // don't write empty cells that do no need styling
228
                // NOTE: not appending to $cellXML is the right behavior!!
229 1
                $cellXML = '';
230
            }
231
        } else {
232 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cell->getValue()));
233
        }
234
235 26
        return $cellXML;
236
    }
237
238
    /**
239
     * Returns the XML fragment for a cell containing a non empty string
240
     *
241
     * @param string $cellValue The cell value
242
     * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell
243
     * @return string The XML fragment representing the cell
244
     */
245 26
    private function getCellXMLFragmentForNonEmptyString($cellValue)
246
    {
247 26
        if ($this->stringHelper->getStringLength($cellValue) > self::MAX_CHARACTERS_PER_CELL) {
248
            throw new InvalidArgumentException('Trying to add a value that exceeds the maximum number of characters allowed in a cell (32,767)');
249
        }
250
251 26
        if ($this->shouldUseInlineStrings) {
252 23
            $cellXMLFragment = ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>';
253
        } else {
254 3
            $sharedStringId = $this->sharedStringsManager->writeString($cellValue);
255 3
            $cellXMLFragment = ' t="s"><v>' . $sharedStringId . '</v></c>';
256
        }
257
258 26
        return $cellXMLFragment;
259
    }
260
261
    /**
262
     * {@inheritdoc}
263
     */
264 30
    public function close(Worksheet $worksheet)
265
    {
266 30
        $worksheetFilePointer = $worksheet->getFilePointer();
267
268 30
        if (!is_resource($worksheetFilePointer)) {
269 3
            return;
270
        }
271
272 27
        fwrite($worksheetFilePointer, '</sheetData>');
273 27
        fwrite($worksheetFilePointer, $worksheet->getMergeXML());
274 27
        fwrite($worksheetFilePointer, '</worksheet>');
275 27
        fclose($worksheetFilePointer);
276 27
    }
277
}
278