WorksheetManager::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

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