Passed
Pull Request — master (#4151)
by Owen
12:28
created

Csv   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 361
Duplicated Lines 0 %

Test Coverage

Coverage 99.03%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 43
eloc 102
c 1
b 0
f 0
dl 0
loc 361
ccs 102
cts 103
cp 0.9903
rs 8.96

26 Methods

Rating   Name   Duplication   Size   Complexity  
A getDelimiter() 0 3 1
A setExcelCompatibility() 0 5 1
A getSheetIndex() 0 3 1
A setDelimiter() 0 5 1
A getUseBOM() 0 3 1
A setPreferHyperlinkToLabel() 0 5 1
A setSheetIndex() 0 5 1
A getOutputEncoding() 0 3 1
A writeLine() 0 37 6
A getIncludeSeparatorLine() 0 3 1
A setOutputEncoding() 0 5 1
A elementToString() 0 7 3
A getVariableColumns() 0 3 1
A setEnclosure() 0 5 1
A getEnclosure() 0 3 1
A setVariableColumns() 0 5 1
A getExcelCompatibility() 0 3 1
A getPreferHyperlinkToLabel() 0 3 1
A setLineEnding() 0 5 1
A __construct() 0 3 1
A getLineEnding() 0 3 1
B save() 0 61 11
A setIncludeSeparatorLine() 0 5 1
A setUseBOM() 0 5 1
A setEnclosureRequired() 0 5 1
A getEnclosureRequired() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Csv often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Csv, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Writer;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
6
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
7
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8
use Stringable;
9
10
class Csv extends BaseWriter
11
{
12
    /**
13
     * PhpSpreadsheet object.
14
     */
15
    private Spreadsheet $spreadsheet;
16
17
    /**
18
     * Delimiter.
19
     */
20
    private string $delimiter = ',';
21
22
    /**
23
     * Enclosure.
24
     */
25
    private string $enclosure = '"';
26
27
    /**
28
     * Line ending.
29
     */
30
    private string $lineEnding = PHP_EOL;
31
32
    /**
33
     * Sheet index to write.
34
     */
35
    private int $sheetIndex = 0;
36
37
    /**
38
     * Whether to write a UTF8 BOM.
39
     */
40
    private bool $useBOM = false;
41
42
    /**
43
     * Whether to write a Separator line as the first line of the file
44
     *     sep=x.
45
     */
46
    private bool $includeSeparatorLine = false;
47
48
    /**
49
     * Whether to write a fully Excel compatible CSV file.
50
     */
51
    private bool $excelCompatibility = false;
52
53
    /**
54
     * Output encoding.
55
     */
56
    private string $outputEncoding = '';
57
58
    /**
59
     * Whether number of columns should be allowed to vary
60
     * between rows, or use a fixed range based on the max
61
     * column overall.
62
     */
63
    private bool $variableColumns = false;
64
65
    private bool $preferHyperlinkToLabel = false;
66
67
    /**
68 30
     * Create a new CSV.
69
     */
70 30
    public function __construct(Spreadsheet $spreadsheet)
71
    {
72
        $this->spreadsheet = $spreadsheet;
73
    }
74
75
    /**
76
     * Save PhpSpreadsheet to file.
77
     *
78 28
     * @param resource|string $filename
79
     */
80 28
    public function save($filename, int $flags = 0): void
81
    {
82
        $this->processFlags($flags);
83 28
84
        // Fetch sheet
85 28
        $sheet = $this->spreadsheet->getSheet($this->sheetIndex);
86 28
87 28
        $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
88
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
89
        $sheet->calculateArrays($this->preCalculateFormulas);
90 28
91
        // Open file
92 27
        $this->openFileHandle($filename);
93 1
94 1
        if ($this->excelCompatibility) {
95 1
            $this->setUseBOM(true); //  Enforce UTF-8 BOM Header
96 1
            $this->setIncludeSeparatorLine(true); //  Set separator line
97 1
            $this->setEnclosure('"'); //  Set enclosure to "
98
            $this->setDelimiter(';'); //  Set delimiter to a semi-colon
99
            $this->setLineEnding("\r\n");
100 27
        }
101
102 3
        if ($this->useBOM) {
103
            // Write the UTF-8 BOM code if required
104
            fwrite($this->fileHandle, "\xEF\xBB\xBF");
105 27
        }
106
107 1
        if ($this->includeSeparatorLine) {
108
            // Write the separator line if required
109
            fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding);
110
        }
111 27
112 27
        //    Identify the range that we need to extract from the worksheet
113
        $maxCol = $sheet->getHighestDataColumn();
114
        $maxRow = $sheet->getHighestDataRow();
115 27
116 27
        // Write rows to file
117 27
        $row = 0;
118 27
        foreach ($sheet->rangeToArrayYieldRows("A1:$maxCol$maxRow", '', $this->preCalculateFormulas) as $cellsArray) {
119 1
            ++$row;
120 1
            if ($this->variableColumns) {
121 1
                $column = $sheet->getHighestDataColumn($row);
122
                if ($column === 'A' && !$sheet->cellExists("A$row")) {
123 1
                    $cellsArray = [];
124
                } else {
125
                    array_splice($cellsArray, Coordinate::columnIndexFromString($column));
126 27
                }
127
            }
128
            if ($this->preferHyperlinkToLabel) {
129 27
                foreach ($cellsArray as $key => $value) {
130 27
                    $url = $sheet->getCell([$key + 1, $row])->getHyperlink()->getUrl();
131
                    if ($url !== '') {
132
                        $cellsArray[$key] = $url;
133 1
                    }
134
                }
135 1
            }
136
            $this->writeLine($this->fileHandle, $cellsArray);
137
        }
138 10
139
        $this->maybeCloseFileHandle();
140 10
        Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
141
    }
142 10
143
    public function getDelimiter(): string
144
    {
145 2
        return $this->delimiter;
146
    }
147 2
148
    public function setDelimiter(string $delimiter): self
149
    {
150 10
        $this->delimiter = $delimiter;
151
152 10
        return $this;
153
    }
154 10
155
    public function getEnclosure(): string
156
    {
157 1
        return $this->enclosure;
158
    }
159 1
160
    public function setEnclosure(string $enclosure = '"'): self
161
    {
162 1
        $this->enclosure = $enclosure;
163
164 1
        return $this;
165
    }
166 1
167
    public function getLineEnding(): string
168
    {
169
        return $this->lineEnding;
170
    }
171
172 1
    public function setLineEnding(string $lineEnding): self
173
    {
174 1
        $this->lineEnding = $lineEnding;
175
176
        return $this;
177
    }
178
179
    /**
180 4
     * Get whether BOM should be used.
181
     */
182 4
    public function getUseBOM(): bool
183
    {
184 4
        return $this->useBOM;
185
    }
186
187
    /**
188
     * Set whether BOM should be used, typically when non-ASCII characters are used.
189
     */
190 1
    public function setUseBOM(bool $useBOM): self
191
    {
192 1
        $this->useBOM = $useBOM;
193
194
        return $this;
195
    }
196
197
    /**
198 1
     * Get whether a separator line should be included.
199
     */
200 1
    public function getIncludeSeparatorLine(): bool
201
    {
202 1
        return $this->includeSeparatorLine;
203
    }
204
205
    /**
206
     * Set whether a separator line should be included as the first line of the file.
207
     */
208 1
    public function setIncludeSeparatorLine(bool $includeSeparatorLine): self
209
    {
210 1
        $this->includeSeparatorLine = $includeSeparatorLine;
211
212
        return $this;
213
    }
214
215
    /**
216
     * Get whether the file should be saved with full Excel Compatibility.
217
     */
218
    public function getExcelCompatibility(): bool
219 1
    {
220
        return $this->excelCompatibility;
221 1
    }
222
223 1
    /**
224
     * Set whether the file should be saved with full Excel Compatibility.
225
     *
226 2
     * @param bool $excelCompatibility Set the file to be written as a fully Excel compatible csv file
227
     *                                Note that this overrides other settings such as useBOM, enclosure and delimiter
228 2
     */
229
    public function setExcelCompatibility(bool $excelCompatibility): self
230
    {
231 2
        $this->excelCompatibility = $excelCompatibility;
232
233 2
        return $this;
234
    }
235 2
236
    public function getSheetIndex(): int
237
    {
238 1
        return $this->sheetIndex;
239
    }
240 1
241
    public function setSheetIndex(int $sheetIndex): self
242
    {
243 1
        $this->sheetIndex = $sheetIndex;
244
245 1
        return $this;
246
    }
247 1
248
    public function getOutputEncoding(): string
249
    {
250
        return $this->outputEncoding;
251
    }
252 4
253
    public function setOutputEncoding(string $outputEnconding): self
254 4
    {
255
        $this->outputEncoding = $outputEnconding;
256 4
257
        return $this;
258
    }
259 3
260
    private bool $enclosureRequired = true;
261 3
262
    public function setEnclosureRequired(bool $value): self
263
    {
264
        $this->enclosureRequired = $value;
265
266
        return $this;
267
    }
268
269 27
    public function getEnclosureRequired(): bool
270
    {
271 27
        return $this->enclosureRequired;
272
    }
273
274
    /**
275 27
     * Convert boolean to TRUE/FALSE; otherwise return element cast to string.
276
     *
277
     * @param null|bool|float|int|string|Stringable $element element to be converted
278
     */
279
    private static function elementToString(mixed $element): string
280
    {
281
        if (is_bool($element)) {
282
            return $element ? 'TRUE' : 'FALSE';
283
        }
284 27
285
        return (string) $element;
286
    }
287 27
288
    /**
289
     * Write line to CSV file.
290 27
     *
291
     * @param resource $fileHandle PHP filehandle
292
     * @param array $values Array containing values in a row
293 27
     */
294 27
    private function writeLine($fileHandle, array $values): void
295
    {
296 27
        // No leading delimiter
297 27
        $delimiter = '';
298
299 27
        // Build the line
300 27
        $line = '';
301
302
        /** @var null|bool|float|int|string|Stringable $element */
303 25
        foreach ($values as $element) {
304 3
            $element = self::elementToString($element);
305
            // Add delimiter
306 24
            $line .= $delimiter;
307
            $delimiter = $this->delimiter;
308
            // Escape enclosures
309
            $enclosure = $this->enclosure;
310 27
            if ($enclosure) {
311
                // If enclosure is not required, use enclosure only if
312
                // element contains newline, delimiter, or enclosure.
313
                if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) {
314 27
                    $enclosure = '';
315
                } else {
316
                    $element = str_replace($enclosure, $enclosure . $enclosure, $element);
317 27
                }
318 1
            }
319
            // Add enclosed string
320 27
            $line .= $enclosure . $element . $enclosure;
321
        }
322
323
        // Add line ending
324
        $line .= $this->lineEnding;
325
326
        // Write to file
327
        if ($this->outputEncoding != '') {
328 1
            $line = mb_convert_encoding($line, $this->outputEncoding);
329
        }
330 1
        fwrite($fileHandle, $line);
331
    }
332
333
    /**
334
     * Get whether number of columns should be allowed to vary
335
     * between rows, or use a fixed range based on the max
336
     * column overall.
337
     */
338 1
    public function getVariableColumns(): bool
339
    {
340 1
        return $this->variableColumns;
341
    }
342 1
343
    /**
344
     * Set whether number of columns should be allowed to vary
345
     * between rows, or use a fixed range based on the max
346
     * column overall.
347
     */
348
    public function setVariableColumns(bool $pValue): self
349
    {
350
        $this->variableColumns = $pValue;
351
352
        return $this;
353
    }
354
355
    /**
356
     * Get whether hyperlink or label should be output.
357
     */
358
    public function getPreferHyperlinkToLabel(): bool
359
    {
360
        return $this->preferHyperlinkToLabel;
361
    }
362
363
    /**
364
     * Set whether hyperlink or label should be output.
365
     */
366
    public function setPreferHyperlinkToLabel(bool $preferHyperlinkToLabel): self
367
    {
368
        $this->preferHyperlinkToLabel = $preferHyperlinkToLabel;
369
370
        return $this;
371
    }
372
}
373