Failed Conditions
Pull Request — master (#738)
by
unknown
02:18
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 42
    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 42
        $this->optionsManager = $optionsManager;
90 42
        $this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS);
91 42
        $this->rowManager = $rowManager;
92 42
        $this->styleManager = $styleManager;
93 42
        $this->styleMerger = $styleMerger;
94 42
        $this->sharedStringsManager = $sharedStringsManager;
95 42
        $this->stringsEscaper = $stringsEscaper;
96 42
        $this->stringHelper = $stringHelper;
97 42
        $this->entityFactory = $entityFactory;
98 42
    }
99
100
    /**
101
     * @return SharedStringsManager
102
     */
103 38
    public function getSharedStringsManager()
104
    {
105 38
        return $this->sharedStringsManager;
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 42
    public function startSheet(Worksheet $worksheet)
112
    {
113 42
        $sheetFilePointer = \fopen($worksheet->getFilePath(), 'w');
114 42
        $this->throwIfSheetFilePointerIsNotAvailable($sheetFilePointer);
115
116 42
        $worksheet->setFilePointer($sheetFilePointer);
117
118 42
        \fwrite($sheetFilePointer, self::SHEET_XML_FILE_HEADER);
119 42
    }
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 42
    private function throwIfSheetFilePointerIsNotAvailable($sheetFilePointer)
129
    {
130 42
        if (!$sheetFilePointer) {
131
            throw new IOException('Unable to open sheet for writing.');
132
        }
133 42
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138 35
    public function addRow(Worksheet $worksheet, Row $row)
139
    {
140 35
        if (!$this->rowManager->isEmpty($row)) {
141 35
            $this->addNonEmptyRow($worksheet, $row);
142
        }
143
144 34
        $worksheet->setLastWrittenRowIndex($worksheet->getLastWrittenRowIndex() + 1);
145 34
    }
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 35
    private function addNonEmptyRow(Worksheet $worksheet, Row $row)
157
    {
158 35
        if (!$worksheet->getExternalSheet()->isSheetStarted()) {
159
            // create nodes for columns widths
160 35
            if ($this->optionsManager->getOption(Options::COLUMN_WIDTHS)) {
161
                $colsString = '<cols>';
162
                foreach ($this->optionsManager->getOption(Options::COLUMN_WIDTHS) as $index => $width) {
163
                    $index++;
164
                    $colsString.= '<col collapsed="false" customWidth="true" hidden="false" outlineLevel="0" style="0" max="'.$index.'" min="'.$index.'"  width="'.$width.'"/>';
165
                }
166
                $colsString.="</cols>";
167
                \fwrite($worksheet->getFilePointer(), $colsString);
168
            }
169
170 35
            \fwrite($worksheet->getFilePointer(), '<sheetData>');
171 35
            $worksheet->getExternalSheet()->setIsSheetStarted(true);
172
        }
173
174 35
        $rowStyle = $row->getStyle();
175 35
        $rowIndexOneBased = $worksheet->getLastWrittenRowIndex() + 1;
176 35
        $numCells = $row->getNumCells();
177 35
        $rowHeight = $row->getHeight();
178
179 35
        $rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '" customHeight="true"' . ' ht="'. $rowHeight  .  '">';
180
181 35
        foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
182 35
            $rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased);
183
        }
184
185 34
        $rowXML .= '</row>';
186
187 34
        $wasWriteSuccessful = \fwrite($worksheet->getFilePointer(), $rowXML);
188 34
        if ($wasWriteSuccessful === false) {
189
            throw new IOException("Unable to write data in {$worksheet->getFilePath()}");
190
        }
191 34
    }
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 35
    private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndexOneBased, $columnIndexZeroBased)
206
    {
207
        // Apply row and extra styles
208 35
        $mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
209 35
        $cell->setStyle($mergedCellAndRowStyle);
210 35
        $newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
211
212 35
        $registeredStyle = $this->styleManager->registerStyle($newCellStyle);
213
214 35
        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 35
    private function getCellXML($rowIndexOneBased, $columnIndexZeroBased, Cell $cell, $styleId)
229
    {
230 35
        $columnLetters = CellHelper::getColumnLettersFromColumnIndex($columnIndexZeroBased);
231 35
        $cellXML = '<c r="' . $columnLetters . $rowIndexOneBased . '"';
232 35
        $cellXML .= ' s="' . $styleId . '"';
233
234 35
        if ($cell->isString()) {
235 32
            $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 34
        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 32
    private function getCellXMLFragmentForNonEmptyString($cellValue)
266
    {
267 32
        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 32
        if ($this->shouldUseInlineStrings) {
272 29
            $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 32
        return $cellXMLFragment;
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284 38
    public function close(Worksheet $worksheet)
285
    {
286 38
        $worksheetFilePointer = $worksheet->getFilePointer();
287
288 38
        if (!\is_resource($worksheetFilePointer)) {
289
            return;
290
        }
291
292 38
        if (!$worksheet->getExternalSheet()->isSheetStarted()) {
293 3
            \fwrite($worksheetFilePointer, '<sheetData>');
294 3
            $worksheet->getExternalSheet()->setIsSheetStarted(true);
295
        }
296
297 38
        \fwrite($worksheetFilePointer, '</sheetData>');
298
299
        // create nodes for merge cells
300 38
        if ($this->optionsManager->getOption(Options::MERGE_CELLS)) {
301
            $mergeCellString = '<mergeCells count="' . \count($this->optionsManager->getOption(Options::MERGE_CELLS)) . '">';
302
            foreach ($this->optionsManager->getOption(Options::MERGE_CELLS) as $values) {
303
                $output = \array_map(function($value){
304
                    return CellHelper::getColumnLettersFromColumnIndex($value[0]) . $value[1];
305
                }, $values);
306
                $mergeCellString.= '<mergeCell ref="' . \implode(':', $output) . '"/>';
307
            }
308
            $mergeCellString.= '</mergeCells>';
309
            \fwrite($worksheet->getFilePointer(), $mergeCellString);
310
        }
311
312 38
        \fwrite($worksheetFilePointer, '</worksheet>');
313 38
        \fclose($worksheetFilePointer);
314 38
    }
315
}
316