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