Completed
Pull Request — master (#808)
by
unknown
11:17
created

WorksheetManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 10
cts 10
cp 1
rs 9.568
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\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 43
     * @param StringHelper $stringHelper
79
     * @param InternalEntityFactory $entityFactory
80
     */
81
    public function __construct(
82
        OptionsManagerInterface $optionsManager,
83
        RowManager $rowManager,
84
        StyleManager $styleManager,
85
        StyleMerger $styleMerger,
86
        SharedStringsManager $sharedStringsManager,
87
        XLSXEscaper $stringsEscaper,
88 43
        StringHelper $stringHelper,
89 43
        InternalEntityFactory $entityFactory
90 43
    ) {
91 43
        $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS);
92 43
        $this->setDefaultColumnWidth($optionsManager->getOption(Options::DEFAULT_COLUMN_WIDTH));
93 43
        $this->setDefaultRowHeight($optionsManager->getOption(Options::DEFAULT_ROW_HEIGHT));
94 43
        $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 43
        $this->rowManager = $rowManager;
96 43
        $this->styleManager = $styleManager;
97
        $this->styleMerger = $styleMerger;
98
        $this->sharedStringsManager = $sharedStringsManager;
99
        $this->stringsEscaper = $stringsEscaper;
100
        $this->stringHelper = $stringHelper;
101 39
        $this->entityFactory = $entityFactory;
102
    }
103 39
104
    /**
105
     * @return SharedStringsManager
106
     */
107
    public function getSharedStringsManager()
108
    {
109 43
        return $this->sharedStringsManager;
110
    }
111 43
112 43
    /**
113
     * {@inheritdoc}
114 43
     */
115
    public function startSheet(Worksheet $worksheet)
116 43
    {
117 43
        $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
118 43
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
119
120
        $worksheet->setFilePointer($sheetFilePointer);
121
122
        \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
123
    }
124
125
    /**
126
     * Writes the sheet data header
127 43
     *
128
     * @param Worksheet $worksheet The worksheet to add the row to
129 43
     * @return void
130
     */
131
    private function ensureSheetDataStated(Worksheet $worksheet)
132 43
    {
133
        if (!$worksheet->getSheetDataStarted()) {
134
            $worksheetFilePointer = $worksheet->getFilePointer();
135
            \fwrite($worksheetFilePointer, $this->getXMLFragmentForDefaultCellSizing());
136
            \fwrite($worksheetFilePointer, $this->getXMLFragmentForColumnWidths());
137 36
            \fwrite($worksheetFilePointer, '<sheetData>');
138
            $worksheet->setSheetDataStarted(true);
139 36
        }
140 36
    }
141
142
    /**
143 35
     * Checks if the sheet has been sucessfully created. Throws an exception if not.
144 35
     *
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
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
150
    {
151
        if (!$sheetFilePointer) {
152
            throw new IOException('Unable to open sheet for writing.');
153
        }
154
    }
155 36
156
    /**
157 36
     * {@inheritdoc}
158 36
     */
159 36
    public function addRow(Worksheet $worksheet, Row $row)
160
    {
161 36
        if (!$this->rowManager->isEmpty($row)) {
162
            $this->addNonEmptyRow($worksheet, $row);
163 36
        }
164 36
165 36
        $worksheet->setLastWrittenRowIndex($worksheet->getLastWrittenRowIndex() + 1);
166 36
    }
167 35
168
    /**
169 36
     * Adds non empty row to the worksheet.
170
     *
171
     * @param Worksheet $worksheet The worksheet to add the row to
172 35
     * @param Row $row The row to be written
173
     * @throws InvalidArgumentException If a cell value's type is not supported
174 35
     * @throws IOException If the data cannot be written
175 35
     * @return void
176
     */
177
    private function addNonEmptyRow(Worksheet $worksheet, Row $row)
178 35
    {
179
        $this->ensureSheetDataStated($worksheet);
180
        $sheetFilePointer = $worksheet->getFilePointer();
181
        $rowStyle = $row->getStyle();
182
        $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1;
183
        $numCells = $row->getNumCells();
184
185
        $hasCustomHeight = $this->defaultRowHeight > 0 ? '1' : '0';
186
        $rowXML = "<row r=\"{$rowIndexOneBased}\" spans=\"1:{$numCells}\" customHeight=\"{$hasCustomHeight}\">";
187
188
        foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
189 36
            $registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle);
190
            $cellStyle = $registeredStyle->getStyle();
191 36
            if ($registeredStyle->isMatchingRowStyle()) {
192 36
                $rowStyle = $cellStyle; // Replace actual rowStyle (possibly with null id) by registered style (with id)
193 35
            }
194
            $rowXML .= $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $cellStyle->getId());
195 35
        }
196
197 35
        $rowXML .= '</row>';
198 1
199
        $wasWriteSuccessful = \fwrite($sheetFilePointer, $rowXML);
200 35
        if ($wasWriteSuccessful === false) {
201 35
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
202
        }
203
    }
204 3
205 3
    /**
206
     * Applies styles to the given style, merging the cell's style with its row's style
207 3
     *
208
     * @param Cell $cell
209 3
     * @param Style $rowStyle
210
     *
211
     * @throws InvalidArgumentException If the given value cannot be processed
212 3
     * @return RegisteredStyle
213
     */
214
    private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : RegisteredStyle
215 3
    {
216
        $isMatchingRowStyle = false;
217
        if ($cell->getStyle()->isEmpty()) {
218 36
            $cell->setStyle($rowStyle);
219
220
            $possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
221
222
            if ($possiblyUpdatedStyle->isUpdated()) {
223
                $registeredStyle = $this->styleManager->registerStyle($possiblyUpdatedStyle->getStyle());
224
            } else {
225
                $registeredStyle = $this->styleManager->registerStyle($rowStyle);
226
                $isMatchingRowStyle = true;
227
            }
228
        } else {
229
            $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
230
            $cell->setStyle($mergedCellAndRowStyle);
231
232 36
            $possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
233
234 36
            if ($possiblyUpdatedStyle->isUpdated()) {
235 36
                $newCellStyle = $possiblyUpdatedStyle->getStyle();
236 36
            } else {
237
                $newCellStyle = $mergedCellAndRowStyle;
238 36
            }
239 32
240 7
            $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
241 1
        }
242 7
243 3
        return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
244 5
    }
245
246 1
    /**
247 4
     * Builds and returns xml for a single cell.
248 2
     *
249 1
     * @param int $rowIndexOneBased
250
     * @param int $columnIndexZeroBased
251
     * @param Cell $cell
252
     * @param int $styleId
253 2
     *
254
     * @throws InvalidArgumentException If the given value cannot be processed
255
     * @return string
256 2
     */
257
    private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId)
258
    {
259 35
        $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased);
260
        $cellXML = '<c r="' . $columnLetters . $rowIndexOneBased . '"';
261
        $cellXML .= ' s="' . $styleId . '"';
262
263
        if ($cell->isString()) {
264
            $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue());
265
        } elseif ($cell->isBoolean()) {
266
            $cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
267
        } elseif ($cell->isNumeric()) {
268
            $cellXML .= '><v>' . $this->stringHelper->formatNumericValue($cell->getValue()) . '</v></c>';
269 32
        } elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
270
            // only writes the error value if it's a string
271 32
            $cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
272
        } elseif ($cell->isEmpty()) {
273
            if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) {
274
                $cellXML .= '/>';
275 32
            } else {
276 29
                // don't write empty cells that do no need styling
277
                // NOTE: not appending to $cellXML is the right behavior!!
278 3
                $cellXML = '';
279 3
            }
280
        } else {
281
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
282 32
        }
283
284
        return $cellXML;
285
    }
286
287
    /**
288 39
     * Returns the XML fragment for a cell containing a non empty string
289
     *
290 39
     * @param string $cellValue The cell value
291
     * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell
292 39
     * @return string The XML fragment representing the cell
293
     */
294
    private function getCellXMLFragmentForNonEmptyString($cellValue)
295
    {
296 39
        if ($this->stringHelper->getStringLength($cellValue) > self::MAX_CHARACTERS_PER_CELL) {
297 39
            throw new InvalidArgumentException('Trying to add a value that exceeds the maximum number of characters allowed in a cell (32,767)');
298 39
        }
299 39
300
        if ($this->shouldUseInlineStrings) {
301
            $cellXMLFragment = ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>';
302
        } else {
303
            $sharedStringId = $this->sharedStringsManager->writeString($cellValue);
304
            $cellXMLFragment = ' t="s"><v>' . $sharedStringId . '</v></c>';
305
        }
306
307
        return $cellXMLFragment;
308
    }
309
310
    /**
311
     * Construct column width references xml to inject into worksheet xml file
312
     *
313
     * @return string
314
     */
315
    public function getXMLFragmentForColumnWidths()
316
    {
317
        if (empty($this->columnWidths)) {
318
            return '';
319
        }
320
        $xml = '<cols>';
321
        foreach ($this->columnWidths as $entry) {
322
            $xml .= '<col min="' . $entry[0] . '" max="' . $entry[1] . '" width="' . $entry[2] . '" customWidth="true"/>';
323
        }
324
        $xml .= '</cols>';
325
326
        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
    public function getXMLFragmentForDefaultCellSizing()
335
    {
336
        $rowHeightXml = empty($this->defaultRowHeight) ? '' : " defaultRowHeight=\"{$this->defaultRowHeight}\"";
337
        $colWidthXml = empty($this->defaultColumnWidth) ? '' : " defaultColWidth=\"{$this->defaultColumnWidth}\"";
338
        if (empty($colWidthXml) && empty($rowHeightXml)) {
339
            return '';
340
        }
341
        // Ensure that the required defaultRowHeight is set
342
        $rowHeightXml = empty($rowHeightXml) ? ' defaultRowHeight="0"' : $rowHeightXml;
343
344
        return "<sheetFormatPr{$colWidthXml}{$rowHeightXml}/>";
345
    }
346
347
    /**
348
     * {@inheritdoc}
349
     */
350
    public function close(Worksheet $worksheet)
351
    {
352
        $worksheetFilePointer = $worksheet->getFilePointer();
353
354
        if (!\is_resource($worksheetFilePointer)) {
355
            return;
356
        }
357
        $this->ensureSheetDataStated($worksheet);
358
        \fwrite($worksheetFilePointer, '</sheetData>');
359
        \fwrite($worksheetFilePointer, '</worksheet>');
360
        \fclose($worksheetFilePointer);
361
    }
362
}
363