Completed
Pull Request — master (#715)
by
unknown
04:32
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\RegisteredStyle;
18
use Box\Spout\Writer\Common\Manager\ManagesCellSize;
19
use Box\Spout\Writer\Common\Manager\RowManager;
20
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
21
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
22
use Box\Spout\Writer\XLSX\Manager\Style\StyleManager;
23
24
/**
25
 * Class WorksheetManager
26
 * XLSX worksheet manager, providing the interfaces to work with XLSX worksheets.
27
 */
28
class WorksheetManager implements WorksheetManagerInterface
29
{
30
    use ManagesCellSize;
31
32
    /**
33
     * Maximum number of characters a cell can contain
34
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-16c69c74-3d6a-4aaf-ba35-e6eb276e8eaa [Excel 2007]
35
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 [Excel 2010]
36
     * @see https://support.office.com/en-us/article/Excel-specifications-and-limits-ca36e2dc-1f09-4620-b726-67c00b05040f [Excel 2013/2016]
37
     */
38
    const MAX_CHARACTERS_PER_CELL = 32767;
39
40
    const SHEET_XML_FILE_HEADER = <<<'EOD'
41
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
42
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
43
EOD;
44
45
    /** @var bool Whether inline or shared strings should be used */
46
    protected $shouldUseInlineStrings;
47
48
    /** @var RowManager Manages rows */
49
    private $rowManager;
50
51
    /** @var StyleManager Manages styles */
52
    private $styleManager;
53
54
    /** @var StyleMerger Helper to merge styles together */
55
    private $styleMerger;
56
57
    /** @var SharedStringsManager Helper to write shared strings */
58
    private $sharedStringsManager;
59
60
    /** @var XLSXEscaper Strings escaper */
61
    private $stringsEscaper;
62
63
    /** @var StringHelper String helper */
64
    private $stringHelper;
65
66
    /** @var InternalEntityFactory Factory to create entities */
67
    private $entityFactory;
68
69
    /**
70
     * WorksheetManager constructor.
71
     *
72
     * @param OptionsManagerInterface $optionsManager
73
     * @param RowManager $rowManager
74
     * @param StyleManager $styleManager
75
     * @param StyleMerger $styleMerger
76
     * @param SharedStringsManager $sharedStringsManager
77
     * @param XLSXEscaper $stringsEscaper
78
     * @param StringHelper $stringHelper
79
     * @param InternalEntityFactory $entityFactory
80
     */
81 49
    public function __construct(
82
        OptionsManagerInterface $optionsManager,
83
        RowManager $rowManager,
84
        StyleManager $styleManager,
85
        StyleMerger $styleMerger,
86
        SharedStringsManager $sharedStringsManager,
87
        XLSXEscaper $stringsEscaper,
88
        StringHelper $stringHelper,
89
        InternalEntityFactory $entityFactory
90
    ) {
91 49
        $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS);
92 49
        $this->setDefaultColumnWidth($optionsManager->getOption(Options::DEFAULT_COLUMN_WIDTH));
93 49
        $this->setDefaultRowHeight($optionsManager->getOption(Options::DEFAULT_ROW_HEIGHT));
94 49
        $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...
95 49
        $this->rowManager = $rowManager;
96 49
        $this->styleManager = $styleManager;
97 49
        $this->styleMerger = $styleMerger;
98 49
        $this->sharedStringsManager = $sharedStringsManager;
99 49
        $this->stringsEscaper = $stringsEscaper;
100 49
        $this->stringHelper = $stringHelper;
101 49
        $this->entityFactory = $entityFactory;
102 49
    }
103
104
    /**
105
     * @return SharedStringsManager
106
     */
107 45
    public function getSharedStringsManager()
108
    {
109 45
        return $this->sharedStringsManager;
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 49
    public function startSheet(Worksheet $worksheet)
116
    {
117 49
        $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
118 49
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
119
120 49
        $worksheet->setFilePointer($sheetFilePointer);
121
122 49
        \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
123 49
    }
124
125
    /**
126
     * Writes the sheet data header
127
     *
128
     * @param Worksheet $worksheet The worksheet to add the row to
129
     * @return void
130
     */
131 45
    private function ensureSheetDataStated(Worksheet $worksheet)
132
    {
133 45
        if (!$worksheet->getSheetDataStarted()) {
134 45
            $worksheetFilePointer = $worksheet->getFilePointer();
135 45
            \fwrite($worksheetFilePointer, $this->getXMLFragmentForDefaultCellSizing());
136 45
            \fwrite($worksheetFilePointer, $this->getXMLFragmentForColumnWidths());
137 45
            \fwrite($worksheetFilePointer, '<sheetData>');
138 45
            $worksheet->setSheetDataStarted(true);
139
        }
140 45
    }
141
142
    /**
143
     * Checks if the sheet has been sucessfully created. Throws an exception if not.
144
     *
145
     * @param bool|resource $sheetFilePointer Pointer to the sheet data file or FALSE if unable to open the file
146
     * @throws IOException If the sheet data file cannot be opened for writing
147
     * @return void
148
     */
149 49
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
150
    {
151 49
        if (!$sheetFilePointer) {
152
            throw new IOException('Unable to open sheet for writing.');
153
        }
154 49
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159 42
    public function addRow(Worksheet $worksheet, Row $row)
160
    {
161 42
        if (!$this->rowManager->isEmpty($row)) {
162 42
            $this->addNonEmptyRow($worksheet, $row);
163
        }
164
165 41
        $worksheet->setLastWrittenRowIndex($worksheet->getLastWrittenRowIndex() + 1);
166 41
    }
167
168
    /**
169
     * Adds non empty row to the worksheet.
170
     *
171
     * @param Worksheet $worksheet The worksheet to add the row to
172
     * @param Row $row The row to be written
173
     * @throws InvalidArgumentException If a cell value's type is not supported
174
     * @throws IOException If the data cannot be written
175
     * @return void
176
     */
177 42
    private function addNonEmptyRow(Worksheet $worksheet, Row $row)
178
    {
179 42
        $this->ensureSheetDataStated($worksheet);
180 42
        $sheetFilePointer = $worksheet->getFilePointer();
181 42
        $rowStyle = $row->getStyle();
182 42
        $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1;
183 42
        $numCells = $row->getNumCells();
184
185 42
        $hasCustomHeight = $this->defaultRowHeight > 0 ? '1' : '0';
186 42
        $rowXML = "<row r=\"{$rowIndexOneBased}\" spans=\"1:{$numCells}\" customHeight=\"{$hasCustomHeight}\">";
187
188 42
        foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
189 42
            $registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle);
190 42
            $cellStyle = $registeredStyle->getStyle();
191 42
            if ($registeredStyle->isMatchingRowStyle()) {
192 41
                $rowStyle = $cellStyle; // Replace actual rowStyle (possibly with null id) by registered style (with id)
193
            }
194 42
            $rowXML .= $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $cellStyle->getId());
195
        }
196
197 41
        $rowXML .= '</row>';
198
199 41
        $wasWriteSuccessful = \fwrite($sheetFilePointer, $rowXML);
200 41
        if ($wasWriteSuccessful === false) {
201
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
202
        }
203 41
    }
204
205
    /**
206
     * Applies styles to the given style, merging the cell's style with its row's style
207
     *
208
     * @param Cell $cell
209
     * @param Style $rowStyle
210
     *
211
     * @throws InvalidArgumentException If the given value cannot be processed
212
     * @return RegisteredStyle
213
     */
214 42
    private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : RegisteredStyle
215
    {
216 42
        $isMatchingRowStyle = false;
217 42
        if ($cell->getStyle()->isEmpty()) {
218 41
            $cell->setStyle($rowStyle);
219
220 41
            $possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
221
222 41
            if ($possiblyUpdatedStyle->isUpdated()) {
223 1
                $registeredStyle = $this->styleManager->registerStyle($possiblyUpdatedStyle->getStyle());
224
            } else {
225 41
                $registeredStyle = $this->styleManager->registerStyle($rowStyle);
226 41
                $isMatchingRowStyle = true;
227
            }
228
        } else {
229 3
            $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
230 3
            $cell->setStyle($mergedCellAndRowStyle);
231
232 3
            $possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
233
234 3
            if ($possiblyUpdatedStyle->isUpdated()) {
235
                $newCellStyle = $possiblyUpdatedStyle->getStyle();
236
            } else {
237 3
                $newCellStyle = $mergedCellAndRowStyle;
238
            }
239
240 3
            $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
241
        }
242
243 42
        return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
244
    }
245
246
    /**
247
     * Builds and returns xml for a single cell.
248
     *
249
     * @param int $rowIndexOneBased
250
     * @param int $columnIndexZeroBased
251
     * @param Cell $cell
252
     * @param int $styleId
253
     *
254
     * @throws InvalidArgumentException If the given value cannot be processed
255
     * @return string
256
     */
257 42
    private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId)
258
    {
259 42
        $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased);
260 42
        $cellXML = '<c r="' . $columnLetters . $rowIndexOneBased . '"';
261 42
        $cellXML .= ' s="' . $styleId . '"';
262
263 42
        if ($cell->isString()) {
264 38
            $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue());
265 7
        } elseif ($cell->isBoolean()) {
266 1
            $cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
267 7
        } elseif ($cell->isNumeric()) {
268 3
            $cellXML .= '><v>' . $this->stringHelper->formatNumericValue($cell->getValue()) . '</v></c>';
269 5
        } elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
270
            // only writes the error value if it's a string
271 1
            $cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
272 4
        } elseif ($cell->isEmpty()) {
273 2
            if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) {
274 1
                $cellXML .= '/>';
275
            } else {
276
                // don't write empty cells that do no need styling
277
                // NOTE: not appending to $cellXML is the right behavior!!
278 2
                $cellXML = '';
279
            }
280
        } else {
281 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
282
        }
283
284 41
        return $cellXML;
285
    }
286
287
    /**
288
     * Returns the XML fragment for a cell containing a non empty string
289
     *
290
     * @param string $cellValue The cell value
291
     * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell
292
     * @return string The XML fragment representing the cell
293
     */
294 38
    private function getCellXMLFragmentForNonEmptyString($cellValue)
295
    {
296 38
        if ($this->stringHelper->getStringLength($cellValue) > self::MAX_CHARACTERS_PER_CELL) {
297
            throw new InvalidArgumentException('Trying to add a value that exceeds the maximum number of characters allowed in a cell (32,767)');
298
        }
299
300 38
        if ($this->shouldUseInlineStrings) {
301 35
            $cellXMLFragment = ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>';
302
        } else {
303 3
            $sharedStringId = $this->sharedStringsManager->writeString($cellValue);
304 3
            $cellXMLFragment = ' t="s"><v>' . $sharedStringId . '</v></c>';
305
        }
306
307 38
        return $cellXMLFragment;
308
    }
309
310
    /**
311
     * Construct column width references xml to inject into worksheet xml file
312
     *
313
     * @return string
314
     */
315 45
    public function getXMLFragmentForColumnWidths()
316
    {
317 45
        if (empty($this->columnWidths)) {
318 41
            return '';
319
        }
320 4
        $xml = '<cols>';
321 4
        foreach ($this->columnWidths as $entry) {
322 4
            $xml .= '<col min="' . $entry[0] . '" max="' . $entry[1] . '" width="' . $entry[2] . '" customWidth="true"/>';
323
        }
324 4
        $xml .= '</cols>';
325
326 4
        return $xml;
327
    }
328
329
    /**
330
     * Constructs default row height and width xml to inject into worksheet xml file
331
     *
332
     * @return string
333
     */
334 45
    public function getXMLFragmentForDefaultCellSizing()
335
    {
336 45
        $rowHeightXml = empty($this->defaultRowHeight) ? '' : " defaultRowHeight=\"{$this->defaultRowHeight}\"";
337 45
        $colWidthXml = empty($this->defaultColumnWidth) ? '' : " defaultColWidth=\"{$this->defaultColumnWidth}\"";
338 45
        if (empty($colWidthXml) && empty($rowHeightXml)) {
339 43
            return '';
340
        }
341
        // Ensure that the required defaultRowHeight is set
342 2
        $rowHeightXml = empty($rowHeightXml) ? ' defaultRowHeight="0"' : $rowHeightXml;
343
344 2
        return "<sheetFormatPr{$colWidthXml}{$rowHeightXml}/>";
345
    }
346
347
    /**
348
     * {@inheritdoc}
349
     */
350 45
    public function close(Worksheet $worksheet)
351
    {
352 45
        $worksheetFilePointer = $worksheet->getFilePointer();
353
354 45
        if (!\is_resource($worksheetFilePointer)) {
355
            return;
356
        }
357 45
        $this->ensureSheetDataStated($worksheet);
358 45
        \fwrite($worksheetFilePointer, '</sheetData>');
359 45
        \fwrite($worksheetFilePointer, '</worksheet>');
360 45
        \fclose($worksheetFilePointer);
361 45
    }
362
}
363