Completed
Pull Request — master (#738)
by
unknown
02:20
created

WorksheetManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 1

Importance

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