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