Failed Conditions
Pull Request — master (#715)
by
unknown
03:04
created

WorksheetManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 22
c 0
b 0
f 0
ccs 13
cts 13
cp 1
rs 9.568
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\ManagesCellSize;
18
use Box\Spout\Writer\Common\Manager\RowManager;
19
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
20
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
21
use Box\Spout\Writer\XLSX\Manager\Style\StyleManager;
22
23
/**
24
 * Class WorksheetManager
25
 * XLSX worksheet manager, providing the interfaces to work with XLSX worksheets.
26
 */
27
class WorksheetManager implements WorksheetManagerInterface
28
{
29
    use ManagesCellSize;
30
31
    /**
32
     * Maximum number of characters a cell can contain
33
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-16c69c74-3d6a-4aaf-ba35-e6eb276e8eaa [Excel 2007]
34
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 [Excel 2010]
35
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-ca36e2dc-1f09-4620-b726-67c00b05040f [Excel 2013/2016]
36
     */
37
    const MAX_CHARACTERS_PER_CELL = 32767;
38
39
    const SHEET_XML_FILE_HEADER = <<<'EOD'
40
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
41
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
42
EOD;
43
44
    /** @var bool Whether inline or shared strings should be used */
45
    protected $shouldUseInlineStrings;
46
47
    /** @var RowManager Manages rows */
48
    private $rowManager;
49
50
    /** @var StyleManager Manages styles */
51
    private $styleManager;
52
53
    /** @var StyleMerger Helper to merge styles together */
54
    private $styleMerger;
55
56
    /** @var SharedStringsManager Helper to write shared strings */
57
    private $sharedStringsManager;
58
59
    /** @var XLSXEscaper Strings escaper */
60
    private $stringsEscaper;
61
62
    /** @var StringHelper String helper */
63
    private $stringHelper;
64
65
    /** @var InternalEntityFactory Factory to create entities */
66
    private $entityFactory;
67
68
    /**
69
     * WorksheetManager constructor.
70
     *
71
     * @param OptionsManagerInterface $optionsManager
72
     * @param RowManager $rowManager
73
     * @param StyleManager $styleManager
74
     * @param StyleMerger $styleMerger
75
     * @param SharedStringsManager $sharedStringsManager
76
     * @param XLSXEscaper $stringsEscaper
77
     * @param StringHelper $stringHelper
78
     * @param InternalEntityFactory $entityFactory
79
     */
80 46
    public function __construct(
81
        OptionsManagerInterface $optionsManager,
82
        RowManager $rowManager,
83
        StyleManager $styleManager,
84
        StyleMerger $styleMerger,
85
        SharedStringsManager $sharedStringsManager,
86
        XLSXEscaper $stringsEscaper,
87
        StringHelper $stringHelper,
88
        InternalEntityFactory $entityFactory
89
    ) {
90 46
        $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS);
91 46
        $this->setDefaultColumnWidth($optionsManager->getOption(Options::DEFAULT_COLUMN_WIDTH));
92 46
        $this->setDefaultRowHeight($optionsManager->getOption(Options::DEFAULT_ROW_HEIGHT));
93 46
        $this->columnWidths = $optionsManager->getOption(Options::COLUMN_WIDTHS) ?? [];
0 ignored issues
show
Documentation Bug introduced by
It seems like $optionsManager->getOpti...LUMN_WIDTHS) ?? array() of type * is incompatible with the declared type array of property $columnWidths.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94 46
        $this->rowManager = $rowManager;
95 46
        $this->styleManager = $styleManager;
96 46
        $this->styleMerger = $styleMerger;
97 46
        $this->sharedStringsManager = $sharedStringsManager;
98 46
        $this->stringsEscaper = $stringsEscaper;
99 46
        $this->stringHelper = $stringHelper;
100 46
        $this->entityFactory = $entityFactory;
101 46
    }
102
103
    /**
104
     * @return SharedStringsManager
105
     */
106 42
    public function getSharedStringsManager()
107
    {
108 42
        return $this->sharedStringsManager;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 46
    public function startSheet(Worksheet $worksheet)
115
    {
116 46
        $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
117 46
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
118
119 46
        $worksheet->setFilePointer($sheetFilePointer);
120
121 46
        \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
122 46
    }
123
124
    /**
125
     * Writes the sheet data header
126
     * 
127
     * @param Worksheet $worksheet The worksheet to add the row to
128
     * @return void
129
     */
130 42
    private function ensureSheetDataStated(Worksheet $worksheet) {
131 42
        if (!$worksheet->getSheetDataStarted()) {
132 42
          $worksheetFilePointer = $worksheet->getFilePointer();
133 42
          \fwrite($worksheetFilePointer, $this->getXMLFragmentForDefaultCellSizing());
134 42
          \fwrite($worksheetFilePointer, $this->getXMLFragmentForColumnWidths());
135 42
          \fwrite($worksheetFilePointer, '<sheetData>');
136 42
          $worksheet->setSheetDataStarted(true);
137
        }
138 42
    }
139
140
    /**
141
     * Checks if the sheet has been sucessfully created. Throws an exception if not.
142
     *
143
     * @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
144
     * @throws IOException If the sheet data file cannot be opened for writing
145
     * @return void
146
     */
147 46
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
148
    {
149 46
        if (!$sheetFilePointer) {
150
            throw new IOException('Unable to open sheet for writing.');
151
        }
152 46
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157 39
    public function addRow(Worksheet $worksheet, Row $row)
158
    {
159 39
        if (!$this->rowManager->isEmpty($row)) {
160 39
            $this->addNonEmptyRow($worksheet, $row);
161
        }
162
163 38
        $worksheet->setLastWrittenRowIndex($worksheet->getLastWrittenRowIndex() + 1);
164 38
    }
165
166
    /**
167
     * Adds non empty row to the worksheet.
168
     *
169
     * @param Worksheet $worksheet The worksheet to add the row to
170
     * @param Row $row The row to be written
171
     * @throws InvalidArgumentException If a cell value's type is not supported
172
     * @throws IOException If the data cannot be written
173
     * @return void
174
     */
175 39
    private function addNonEmptyRow(Worksheet $worksheet, Row $row)
176
    {
177 39
        $this->ensureSheetDataStated($worksheet);
178 39
        $sheetFilePointer = $worksheet->getFilePointer();
179 39
        $rowStyle = $row->getStyle();
180 39
        $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1;
181 39
        $numCells = $row->getNumCells();
182
183 39
        $hasCustomHeight = $this->defaultRowHeight > 0 ? '1' : '0';
184 39
        $rowXML = "<row r=\"{$rowIndexOneBased}\" spans=\"1:{$numCells}\" customHeight=\"{$hasCustomHeight}\">";
185
186 39
        foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
187 39
            $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased);
188
        }
189
190 38
        $rowXML .= '</row>';
191
192 38
        $wasWriteSuccessful = \fwrite($sheetFilePointer, $rowXML);
193 38
        if ($wasWriteSuccessful === false) {
194
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
195
        }
196 38
    }
197
198
    /**
199
     * Applies styles to the given style, merging the cell's style with its row's style
200
     * Then builds and returns xml for the cell.
201
     *
202
     * @param Cell $cell
203
     * @param Style $rowStyle
204
     * @param int $rowIndexOneBased
205
     * @param int $columnIndexZeroBased
206
     *
207
     * @throws InvalidArgumentException If the given value cannot be processed
208
     * @return string
209
     */
210 39
    private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndexOneBased, $columnIndexZeroBased)
211
    {
212
        // Apply row and extra styles
213 39
        $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
214 39
        $cell->setStyle($mergedCellAndRowStyle);
215 39
        $newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
216
217 39
        $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
218
219 39
        return $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $registeredStyle->getId());
220
    }
221
222
    /**
223
     * Builds and returns xml for a single cell.
224
     *
225
     * @param int $rowIndexOneBased
226
     * @param int $columnIndexZeroBased
227
     * @param Cell $cell
228
     * @param int $styleId
229
     *
230
     * @throws InvalidArgumentException If the given value cannot be processed
231
     * @return string
232
     */
233 39
    private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId)
234
    {
235 39
        $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased);
236 39
        $cellXML = '<c r="' . $columnLetters . $rowIndexOneBased . '"';
237 39
        $cellXML .= ' s="' . $styleId . '"';
238
239 39
        if ($cell->isString()) {
240 36
            $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue());
241 6
        } elseif ($cell->isBoolean()) {
242 1
            $cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
243 6
        } elseif ($cell->isNumeric()) {
244 2
            $cellXML .= '><v>' . $cell->getValue() . '</v></c>';
245 5
        } elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
246
            // only writes the error value if it's a string
247 1
            $cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
248 4
        } elseif ($cell->isEmpty()) {
249 2
            if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) {
250 1
                $cellXML .= '/>';
251
            } else {
252
                // don't write empty cells that do no need styling
253
                // NOTE: not appending to $cellXML is the right behavior!!
254 2
                $cellXML = '';
255
            }
256
        } else {
257 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
258
        }
259
260 38
        return $cellXML;
261
    }
262
263
    /**
264
     * Returns the XML fragment for a cell containing a non empty string
265
     *
266
     * @param string $cellValue The cell value
267
     * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell
268
     * @return string The XML fragment representing the cell
269
     */
270 36
    private function getCellXMLFragmentForNonEmptyString($cellValue)
271
    {
272 36
        if ($this->stringHelper->getStringLength($cellValue) > self::MAX_CHARACTERS_PER_CELL) {
273
            throw new InvalidArgumentException('Trying to add a value that exceeds the maximum number of characters allowed in a cell (32,767)');
274
        }
275
276 36
        if ($this->shouldUseInlineStrings) {
277 33
            $cellXMLFragment = ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>';
278
        } else {
279 3
            $sharedStringId = $this->sharedStringsManager->writeString($cellValue);
280 3
            $cellXMLFragment = ' t="s"><v>' . $sharedStringId . '</v></c>';
281
        }
282
283 36
        return $cellXMLFragment;
284
    }
285
286
    /**
287
     * Construct column width references xml to inject into worksheet xml file
288
     *
289
     * @return string
290
     */
291 42
    public function getXMLFragmentForColumnWidths()
292
    {
293 42
        if (empty($this->columnWidths)) {
294 38
            return '';
295
        }
296 4
        $xml = '<cols>';
297 4
        foreach ($this->columnWidths as $entry) {
298 4
            $xml .= '<col min="' . $entry[0] . '" max="' . $entry[1] . '" width="' . $entry[2] . '" customWidth="true"/>';
299
        }
300 4
        $xml .= '</cols>';
301
302 4
        return $xml;
303
    }
304
305
    /**
306
     * Constructs default row height and width xml to inject into worksheet xml file
307
     *
308
     * @return string
309
     */
310 42
    public function getXMLFragmentForDefaultCellSizing()
311
    {
312 42
        $rowHeightXml = empty($this->defaultRowHeight) ? '' : " defaultRowHeight=\"{$this->defaultRowHeight}\"";
313 42
        $colWidthXml = empty($this->defaultColumnWidth) ? '' : " defaultColWidth=\"{$this->defaultColumnWidth}\"";
314 42
        if (empty($colWidthXml) && empty($rowHeightXml)) {
315 42
            return '';
316
        }
317
        // Ensure that the required defaultRowHeight is set
318
        $rowHeightXml = empty($rowHeightXml) ? ' defaultRowHeight="0"' : $rowHeightXml;
319
320
        return "<sheetFormatPr{$colWidthXml}{$rowHeightXml}/>";
321
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326 42
    public function close(Worksheet $worksheet)
327
    {
328 42
        $worksheetFilePointer = $worksheet->getFilePointer();
329
330 42
        if (!\is_resource($worksheetFilePointer)) {
331
            return;
332
        }
333 42
        $this->ensureSheetDataStated($worksheet);
334 42
        \fwrite($worksheetFilePointer, '</sheetData>');
335 42
        \fwrite($worksheetFilePointer, '</worksheet>');
336 42
        \fclose($worksheetFilePointer);
337 42
    }
338
}
339