Passed
Pull Request — master (#4142)
by Owen
16:30 queued 03:39
created

Sample   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 278
Duplicated Lines 0 %

Test Coverage

Coverage 97.87%

Importance

Changes 0
Metric Value
wmc 48
eloc 105
c 0
b 0
f 0
dl 0
loc 278
ccs 92
cts 94
cp 0.9787
rs 8.5599

19 Methods

Rating   Name   Duplication   Size   Complexity  
A isIndex() 0 3 1
A isCli() 0 3 1
A isDirOrMkdir() 0 3 2
A logEndingNotes() 0 4 1
A getTemporaryFilename() 0 11 2
A getPageTitle() 0 3 2
A getPageHeading() 0 3 2
B getSamples() 0 35 7
A getScriptFilename() 0 3 1
A log() 0 4 3
A logRead() 0 7 1
A displayGrid() 0 4 1
A logWrite() 0 11 2
A getFilename() 0 5 1
A getTemporaryFolder() 0 8 2
A logCalculationResult() 0 11 2
B write() 0 33 8
A titles() 0 6 2
B renderChart() 0 34 7

How to fix   Complexity   

Complex Class

Complex classes like Sample 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 Sample, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Helper;
4
5
use PhpOffice\PhpSpreadsheet\Chart\Chart;
6
use PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer;
7
use PhpOffice\PhpSpreadsheet\IOFactory;
8
use PhpOffice\PhpSpreadsheet\Settings;
9
use PhpOffice\PhpSpreadsheet\Spreadsheet;
10
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
11
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
12
use PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf;
13
use RecursiveDirectoryIterator;
14
use RecursiveIteratorIterator;
15
use RecursiveRegexIterator;
16
use ReflectionClass;
17
use RegexIterator;
18
use RuntimeException;
19
use Throwable;
20
21
/**
22
 * Helper class to be used in sample code.
23
 */
24
class Sample
25
{
26
    /**
27
     * Returns whether we run on CLI or browser.
28
     */
29 288
    public function isCli(): bool
30
    {
31 288
        return PHP_SAPI === 'cli';
32
    }
33
34
    /**
35
     * Return the filename currently being executed.
36
     */
37 1
    public function getScriptFilename(): string
38
    {
39 1
        return basename($_SERVER['SCRIPT_FILENAME'], '.php');
40
    }
41
42
    /**
43
     * Whether we are executing the index page.
44
     */
45 1
    public function isIndex(): bool
46
    {
47 1
        return $this->getScriptFilename() === 'index';
48
    }
49
50
    /**
51
     * Return the page title.
52
     */
53 1
    public function getPageTitle(): string
54
    {
55 1
        return $this->isIndex() ? 'PHPSpreadsheet' : $this->getScriptFilename();
56
    }
57
58
    /**
59
     * Return the page heading.
60
     */
61 1
    public function getPageHeading(): string
62
    {
63 1
        return $this->isIndex() ? '' : '<h1>' . str_replace('_', ' ', $this->getScriptFilename()) . '</h1>';
64
    }
65
66
    /**
67
     * Returns an array of all known samples.
68
     *
69
     * @return string[][] [$name => $path]
70
     */
71 1
    public function getSamples(): array
72
    {
73
        // Populate samples
74 1
        $baseDir = realpath(__DIR__ . '/../../../samples');
75 1
        if ($baseDir === false) {
76
            // @codeCoverageIgnoreStart
77
            throw new RuntimeException('realpath returned false');
78
            // @codeCoverageIgnoreEnd
79
        }
80 1
        $directory = new RecursiveDirectoryIterator($baseDir);
81 1
        $iterator = new RecursiveIteratorIterator($directory);
82 1
        $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH);
83
84 1
        $files = [];
85
        /** @var string[] $file */
86 1
        foreach ($regex as $file) {
87 1
            $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0]));
88 1
            $info = pathinfo($file);
89 1
            $category = str_replace('_', ' ', $info['dirname'] ?? '');
90 1
            $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename']));
91 1
            if (!in_array($category, ['.', 'bootstrap', 'templates']) && $name !== 'Header') {
92 1
                if (!isset($files[$category])) {
93 1
                    $files[$category] = [];
94
                }
95 1
                $files[$category][$name] = $file;
96
            }
97
        }
98
99
        // Sort everything
100 1
        ksort($files);
101 1
        foreach ($files as &$f) {
102 1
            asort($f);
103
        }
104
105 1
        return $files;
106
    }
107
108
    /**
109
     * Write documents.
110
     *
111
     * @param string[] $writers
112
     */
113 116
    public function write(Spreadsheet $spreadsheet, string $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null, bool $resetActiveSheet = true): void
114
    {
115
        // Set active sheet index to the first sheet, so Excel opens this as the first sheet
116 116
        if ($resetActiveSheet) {
117 113
            $spreadsheet->setActiveSheetIndex(0);
118
        }
119
120
        // Write documents
121 116
        foreach ($writers as $writerType) {
122 116
            $path = $this->getFilename($filename, mb_strtolower($writerType));
123 116
            if (preg_match('/(mpdf|tcpdf)$/', $path)) {
124 1
                $path .= '.pdf';
125
            }
126 116
            $writer = IOFactory::createWriter($spreadsheet, $writerType);
127 116
            $writer->setIncludeCharts($withCharts);
128 116
            if ($writerCallback !== null) {
129 7
                $writerCallback($writer);
130
            }
131 116
            $callStartTime = microtime(true);
132 116
            if (PHP_VERSION_ID >= 80400 && $writer instanceof Dompdf) {
133
                @$writer->save($path);
134
            } else {
135 116
                $writer->save($path);
136
            }
137 116
            $this->logWrite($writer, $path, $callStartTime);
138 116
            if ($this->isCli() === false) {
139
                // @codeCoverageIgnoreStart
140
                echo '<a href="/download.php?type=' . pathinfo($path, PATHINFO_EXTENSION) . '&name=' . basename($path) . '">Download ' . basename($path) . '</a><br />';
141
                // @codeCoverageIgnoreEnd
142
            }
143
        }
144
145 116
        $this->logEndingNotes();
146
    }
147
148 125
    protected function isDirOrMkdir(string $folder): bool
149
    {
150 125
        return \is_dir($folder) || \mkdir($folder);
151
    }
152
153
    /**
154
     * Returns the temporary directory and make sure it exists.
155
     */
156 126
    public function getTemporaryFolder(): string
157
    {
158 126
        $tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
159 126
        if (!$this->isDirOrMkdir($tempFolder)) {
160 1
            throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder));
161
        }
162
163 125
        return $tempFolder;
164
    }
165
166
    /**
167
     * Returns the filename that should be used for sample output.
168
     */
169 123
    public function getFilename(string $filename, string $extension = 'xlsx'): string
170
    {
171 123
        $originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
172
173 123
        return $this->getTemporaryFolder() . '/' . str_replace('.' . $originalExtension, '.' . $extension, basename($filename));
174
    }
175
176
    /**
177
     * Return a random temporary file name.
178
     */
179 7
    public function getTemporaryFilename(string $extension = 'xlsx'): string
180
    {
181 7
        $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-');
182 7
        if ($temporaryFilename === false) {
183
            // @codeCoverageIgnoreStart
184
            throw new RuntimeException('tempnam returned false');
185
            // @codeCoverageIgnoreEnd
186
        }
187 7
        unlink($temporaryFilename);
188
189 7
        return $temporaryFilename . '.' . $extension;
190
    }
191
192 287
    public function log(string $message): void
193
    {
194 287
        $eol = $this->isCli() ? PHP_EOL : '<br />';
195 287
        echo ($this->isCli() ? date('H:i:s ') : '') . $message . $eol;
196
    }
197
198
    /**
199
     * Render chart as part of running chart samples in browser.
200
     * Charts are not rendered in unit tests, which are command line.
201
     *
202
     * @codeCoverageIgnore
203
     */
204
    public function renderChart(Chart $chart, string $fileName, ?Spreadsheet $spreadsheet = null): void
205
    {
206
        if ($this->isCli() === true) {
207
            return;
208
        }
209
        Settings::setChartRenderer(MtJpGraphRenderer::class);
210
211
        $fileName = $this->getFilename($fileName, 'png');
212
        $title = $chart->getTitle();
213
        $caption = null;
214
        if ($title !== null) {
215
            $calculatedTitle = $title->getCalculatedTitle($spreadsheet);
216
            if ($calculatedTitle !== null) {
217
                $caption = $title->getCaption();
218
                $title->setCaption($calculatedTitle);
219
            }
220
        }
221
222
        try {
223
            $chart->render($fileName);
224
            $this->log('Rendered image: ' . $fileName);
225
            $imageData = @file_get_contents($fileName);
226
            if ($imageData !== false) {
227
                echo '<div><img src="data:image/gif;base64,' . base64_encode($imageData) . '" /></div>';
228
            } else {
229
                $this->log('Unable to open chart' . PHP_EOL);
230
            }
231
        } catch (Throwable $e) {
232
            $this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL);
233
        }
234
        if (isset($title, $caption)) {
235
            $title->setCaption($caption);
236
        }
237
        Settings::unsetChartRenderer();
238
    }
239
240 87
    public function titles(string $category, string $functionName, ?string $description = null): void
241
    {
242 87
        $this->log(sprintf('%s Functions:', $category));
243 87
        $description === null
244
            ? $this->log(sprintf('Function: %s()', rtrim($functionName, '()')))
245 87
            : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.')));
246
    }
247
248 36
    public function displayGrid(array $matrix): void
249
    {
250 36
        $renderer = new TextGrid($matrix, $this->isCli());
251 36
        echo $renderer->render();
252
    }
253
254 12
    public function logCalculationResult(
255
        Worksheet $worksheet,
256
        string $functionName,
257
        string $formulaCell,
258
        ?string $descriptionCell = null
259
    ): void {
260 12
        if ($descriptionCell !== null) {
261 12
            $this->log($worksheet->getCell($descriptionCell)->getValueString());
262
        }
263 12
        $this->log($worksheet->getCell($formulaCell)->getValueString());
264 12
        $this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValueString());
265
    }
266
267
    /**
268
     * Log ending notes.
269
     */
270 119
    public function logEndingNotes(): void
271
    {
272
        // Do not show execution time for index
273 119
        $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB');
274
    }
275
276
    /**
277
     * Log a line about the write operation.
278
     */
279 121
    public function logWrite(IWriter $writer, string $path, float $callStartTime): void
280
    {
281 121
        $callEndTime = microtime(true);
282 121
        $callTime = $callEndTime - $callStartTime;
283 121
        $reflection = new ReflectionClass($writer);
284 121
        $format = $reflection->getShortName();
285
286 121
        $codePath = $this->isCli() ? $path : "<code>$path</code>";
287 121
        $message = "Write {$format} format to {$codePath}  in " . sprintf('%.4f', $callTime) . ' seconds';
288
289 121
        $this->log($message);
290
    }
291
292
    /**
293
     * Log a line about the read operation.
294
     */
295 15
    public function logRead(string $format, string $path, float $callStartTime): void
296
    {
297 15
        $callEndTime = microtime(true);
298 15
        $callTime = $callEndTime - $callStartTime;
299 15
        $message = "Read {$format} format from <code>{$path}</code>  in " . sprintf('%.4f', $callTime) . ' seconds';
300
301 15
        $this->log($message);
302
    }
303
}
304