Completed
Pull Request — master (#763)
by Adrien
03:28
created

WorksheetManager::__construct()   A

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\EmptyStyle;
8
use Box\Spout\Common\Entity\Style\Style;
9
use Box\Spout\Common\Exception\InvalidArgumentException;
10
use Box\Spout\Common\Exception\IOException;
11
use Box\Spout\Common\Helper\Escaper\XLSX as XLSXEscaper;
12
use Box\Spout\Common\Helper\StringHelper;
13
use Box\Spout\Common\Manager\OptionsManagerInterface;
14
use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
15
use Box\Spout\Writer\Common\Entity\Options;
16
use Box\Spout\Writer\Common\Entity\Worksheet;
17
use Box\Spout\Writer\Common\Helper\CellHelper;
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 42
    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 42
        $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS);
89 42
        $this->rowManager = $rowManager;
90 42
        $this->styleManager = $styleManager;
91 42
        $this->styleMerger = $styleMerger;
92 42
        $this->sharedStringsManager = $sharedStringsManager;
93 42
        $this->stringsEscaper = $stringsEscaper;
94 42
        $this->stringHelper = $stringHelper;
95 42
        $this->entityFactory = $entityFactory;
96 42
    }
97
98
    /**
99
     * @return SharedStringsManager
100
     */
101 38
    public function getSharedStringsManager()
102
    {
103 38
        return $this->sharedStringsManager;
104
    }
105
106
    /**
107
     * {@inheritdoc}
108
     */
109 42
    public function startSheet(Worksheet $worksheet)
110
    {
111 42
        $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
112 42
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
113
114 42
        $worksheet->setFilePointer($sheetFilePointer);
115
116 42
        \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
117 42
        \fwrite($sheetFilePointer, '<sheetData>');
118 42
    }
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 42
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
128
    {
129 42
        if (!$sheetFilePointer) {
130
            throw new IOException('Unable to open sheet for writing.');
131
        }
132 42
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 35
    public function addRow(Worksheet $worksheet, Row $row)
138
    {
139 35
        if (!$this->rowManager->isEmpty($row)) {
140 35
            $this->addNonEmptyRow($worksheet, $row);
141
        }
142
143 34
        $worksheet->setLastWrittenRowIndex($worksheet->getLastWrittenRowIndex() + 1);
144 34
    }
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 35
    private function addNonEmptyRow(Worksheet $worksheet, Row $row)
156
    {
157 35
        $rowStyle = $row->getStyle();
158 35
        $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1;
159 35
        $numCells = $row->getNumCells();
160
161 35
        $rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '">';
162
163 35
        foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
164 35
            $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased);
165
        }
166
167 34
        $rowXML .= '</row>';
168
169 34
        $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $rowXML);
170 34
        if ($wasWriteSuccessful === false) {
171
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
172
        }
173 34
    }
174
175
    /**
176
     * Applies styles to the given style, merging the cell's style with its row's style
177
     * Then builds and returns xml for the cell.
178
     *
179
     * @param Cell  $cell
180
     * @param Style $rowStyle
181
     * @param int   $rowIndexOneBased
182
     * @param int   $columnIndexZeroBased
183
     *
184
     * @throws InvalidArgumentException If the given value cannot be processed
185
     * @return string
186
     */
187 35
    private function applyStyleAndGetCellXML(Cell $cell, Style &$rowStyle, $rowIndexOneBased, $columnIndexZeroBased)
188
    {
189 35
        if ($cell->getStyle() instanceof EmptyStyle) {
190 34
            $cell->setStyle($rowStyle);
191
192 34
            $extraStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
193
194 34
            if ($extraStyle) {
195 1
                $registeredStyle = $this->styleManager->registerStyle($extraStyle);
196
            } else {
197 34
                $registeredStyle = $rowStyle = $this->styleManager->registerStyle($rowStyle);
198
            }
199
        } else {
200 3
            $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
201 3
            $cell->setStyle($mergedCellAndRowStyle);
202
203 3
            $newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell) ?: $mergedCellAndRowStyle;
204 3
            $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
205
        }
206
207 35
        return $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $registeredStyle->getId());
208
    }
209
210
    /**
211
     * Builds and returns xml for a single cell.
212
     *
213
     * @param int  $rowIndexOneBased
214
     * @param int  $columnIndexZeroBased
215
     * @param Cell $cell
216
     * @param int  $styleId
217
     *
218
     * @throws InvalidArgumentException If the given value cannot be processed
219
     * @return string
220
     */
221 35
    private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId)
222
    {
223 35
        $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased);
224 35
        $cellXML = '<c r="' . $columnLetters . $rowIndexOneBased . '"';
225 35
        $cellXML .= ' s="' . $styleId . '"';
226
227 35
        if ($cell->isString()) {
228 32
            $cellXML .= $this->getCellXMLFragmentForNonEmptyString($cell->getValue());
229 6
        } elseif ($cell->isBoolean()) {
230 1
            $cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
231 6
        } elseif ($cell->isNumeric()) {
232 2
            $cellXML .= '><v>' . $cell->getValue() . '</v></c>';
233 5
        } elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
234
            // only writes the error value if it's a string
235 1
            $cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
236 4
        } elseif ($cell->isEmpty()) {
237 2
            if ($this->styleManager->shouldApplyStyleOnEmptyCell($styleId)) {
238 1
                $cellXML .= '/>';
239
            } else {
240
                // don't write empty cells that do no need styling
241
                // NOTE: not appending to $cellXML is the right behavior!!
242 2
                $cellXML = '';
243
            }
244
        } else {
245 2
            throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . \gettype($cell->getValue()));
246
        }
247
248 34
        return $cellXML;
249
    }
250
251
    /**
252
     * Returns the XML fragment for a cell containing a non empty string
253
     *
254
     * @param string $cellValue The cell value
255
     * @throws InvalidArgumentException If the string exceeds the maximum number of characters allowed per cell
256
     * @return string The XML fragment representing the cell
257
     */
258 32
    private function getCellXMLFragmentForNonEmptyString($cellValue)
259
    {
260 32
        if ($this->stringHelper->getStringLength($cellValue) > self::MAX_CHARACTERS_PER_CELL) {
261
            throw new InvalidArgumentException('Trying to add a value that exceeds the maximum number of characters allowed in a cell (32,767)');
262
        }
263
264 32
        if ($this->shouldUseInlineStrings) {
265 29
            $cellXMLFragment = ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>';
266
        } else {
267 3
            $sharedStringId = $this->sharedStringsManager->writeString($cellValue);
268 3
            $cellXMLFragment = ' t="s"><v>' . $sharedStringId . '</v></c>';
269
        }
270
271 32
        return $cellXMLFragment;
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277 38
    public function close(Worksheet $worksheet)
278
    {
279 38
        $worksheetFilePointer = $worksheet->getFilePointer();
280
281 38
        if (!\is_resource($worksheetFilePointer)) {
282
            return;
283
        }
284
285 38
        \fwrite($worksheetFilePointer, '</sheetData>');
286 38
        \fwrite($worksheetFilePointer, '</worksheet>');
287 38
        \fclose($worksheetFilePointer);
288 38
    }
289
}
290