Passed
Push — master ( 5b5c00...4c0c12 )
by Julien
04:52
created

Download   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 202
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 41
eloc 106
c 1
b 0
f 0
dl 0
loc 202
ccs 0
cts 102
cp 0
rs 9.1199

2 Methods

Rating   Name   Duplication   Size   Complexity  
F download() 0 136 22
D getContentType() 0 45 19

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/**
4
 * This file is part of the Zemit Framework.
5
 *
6
 * (c) Zemit Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zemit\Mvc\Controller;
13
14
use Exception;
15
use League\Csv\ByteSequence;
16
use League\Csv\CannotInsertRecord;
17
use League\Csv\CharsetConverter;
18
use League\Csv\InvalidArgument;
19
use League\Csv\Writer;
20
use Shuchkin\SimpleXLSXGen;
21
use Zemit\Support\Slug;
22
23
trait Download {
24
    
25
    use Params;
26
    
27
    /**
28
     * Get the content type based on the given parameters.
29
     *
30
     * @param array|null $params Optional. The parameters to determine the content type. If not provided, it will use the default parameters.
31
     * @return string The content type. Possible values: "json", "csv", "xlsx".
32
     * @throws Exception When an unsupported content type is provided.
33
     */
34
    public function getContentType(?array $params = null): string
35
    {
36
        $params ??= $this->getParams();
37
        
38
        $contentType = strtolower($params['contentType'] ?? $params['content-type'] ?? $this->request->getContentType());
0 ignored issues
show
Bug introduced by
The property request does not exist on Zemit\Mvc\Controller\Download. Did you mean request;?
Loading history...
39
        
40
        switch ($contentType) {
41
            case 'html':
42
            case 'text/html':
43
            case 'application/html':
44
                // html not supported yet
45
                break;
46
            
47
            case 'xml':
48
            case 'text/xml':
49
            case 'application/xml':
50
                // xml not supported yet
51
                break;
52
            
53
            case 'text':
54
            case 'text/plain':
55
                // plain text not supported yet
56
                break;
57
            
58
            case 'json':
59
            case 'text/json':
60
            case 'application/json':
61
                return 'json';
62
            
63
            case 'csv':
64
            case 'text/csv':
65
                return 'csv';
66
            
67
            case 'xlsx':
68
            case 'application/xlsx':
69
            case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
70
                return 'xlsx';
71
            
72
            case 'xls':
73
            case 'application/vnd.ms-excel':
74
                // old xls not supported yet
75
                break;
76
        }
77
        
78
        throw new Exception('`' . $contentType . '` is not supported.', 400);
79
    }
80
    
81
    /**
82
     * Download a JSON / CSV / XLSX
83
     * @TODO optimize this using stream and avoid storage large dataset into variables
84
     * @throws CannotInsertRecord
85
     * @throws InvalidArgument
86
     * @throws \League\Csv\Exception
87
     * @throws Exception
88
     */
89
    public function download(array $list = [], string $fileName = null, string $contentType = null, array $params = null): bool
90
    {
91
        $params ??= $this->getParams();
92
        $contentType ??= $this->getContentType();
93
        $fileName ??= ucfirst(Slug::generate(basename(str_replace('\\', '/', $this->getModelClassName())))) . ' List (' . date('Y-m-d') . ')';
0 ignored issues
show
Bug introduced by
It seems like getModelClassName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

93
        $fileName ??= ucfirst(Slug::generate(basename(str_replace('\\', '/', $this->/** @scrutinizer ignore-call */ getModelClassName())))) . ' List (' . date('Y-m-d') . ')';
Loading history...
94
        
95
        if ($contentType === 'json') {
96
//            $this->response->setJsonContent($list);
97
            $this->response->setContent(json_encode($list, JSON_PRETTY_PRINT, 2048));
0 ignored issues
show
Bug introduced by
The property response does not exist on Zemit\Mvc\Controller\Download. Did you mean response;?
Loading history...
98
            $this->response->setContentType('application/json');
99
            $this->response->setHeader('Content-disposition', 'attachment; filename="' . addslashes($fileName) . '.json"');
100
            $this->response->send();
101
            return true;
102
        }
103
        
104
        $listColumns = [];
105
        if ($contentType === 'csv' || $contentType === 'xlsx') {
106
            foreach ($list as $row) {
107
                foreach (array_keys($row) as $key) {
108
                    $listColumns[$key] = true;
109
                }
110
            }
111
        }
112
        $listColumns = array_keys($listColumns);
113
        
114
        // CSV
115
        if ($contentType === 'csv') {
116
            
117
            // Get CSV custom request parameters
118
            $mode = $params['mode'] ?? null;
119
            $delimiter = $params['delimiter'] ?? null;
120
            $enclosure = $params['enclosure'] ?? null;
121
            $endOfLine = $params['endOfLine'] ?? null;
122
            $escape = $params['escape'] ?? '';
123
            $outputBOM = $params['outputBOM'] ?? null;
124
            $skipIncludeBOM = $params['skipIncludeBOM'] ?? false;
125
            $relaxEnclosure = $params['relaxEnclosure'] ?? false;
126
            $keepEndOfLines = $params['keepEndOfLines'] ?? false;
127
128
//            $csv = Writer::createFromFileObject(new \SplTempFileObject());
129
            $csv = Writer::createFromStream(fopen('php://memory', 'r+'));
130
            
131
            // CSV - MS Excel on MacOS
132
            if ($mode === 'mac') {
133
                $csv->setOutputBOM(ByteSequence::BOM_UTF16_LE); // utf-16
134
                $csv->setDelimiter("\t"); // tabs separated
135
                $csv->setEndOfLine("\r\n"); // end of lines
136
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-16');
137
            }
138
            
139
            // CSV - MS Excel on Windows
140
            else {
141
                $csv->setOutputBOM(ByteSequence::BOM_UTF8); // utf-8
142
                $csv->setDelimiter(','); // comma separated
143
                $csv->setEndOfLine("\r\n"); // end of lines
144
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-8');
145
            }
146
            
147
            // relax enclosure
148
            if ($relaxEnclosure) {
149
                $csv->relaxEnclosure();
150
            }
151
            // force enclosure
152
            else {
153
                $csv->forceEnclosure();
154
            }
155
            // set enclosure
156
            if (isset($enclosure)) {
157
                $csv->setEnclosure($enclosure);
158
            }
159
            // set output bom
160
            if (isset($outputBOM)) {
161
                $csv->setOutputBOM($outputBOM);
162
            }
163
            // set delimiter
164
            if (isset($delimiter)) {
165
                $csv->setDelimiter($delimiter);
166
            }
167
            // send end of line
168
            if (isset($endOfLine)) {
169
                $csv->setEndOfLine($endOfLine);
170
            }
171
            // set escape
172
            if (isset($escape)) {
173
                $csv->setEscape($escape);
174
            }
175
            // skip include bom
176
            if ($skipIncludeBOM) {
177
                $csv->skipInputBOM();
178
            }
179
            // include bom
180
            else {
181
                $csv->includeInputBOM();
182
            }
183
            
184
            // Headers
185
            $csv->insertOne($listColumns);
186
            
187
            foreach ($list as $row) {
188
                $outputRow = [];
189
                foreach ($listColumns as $column) {
190
                    $outputRow[$column] = $row[$column] ?? '';
191
                    
192
                    // sometimes excel can't process the cells multiple lines correctly when loading csv
193
                    // this is why we remove the new lines by default, user can choose to keep them using $keepEndOfLines
194
                    if (!$keepEndOfLines && is_string($outputRow[$column])) {
195
                        $outputRow[$column] = trim(preg_replace('/\s+/', ' ', $outputRow[$column]));
196
                    }
197
                }
198
                $csv->insertOne($outputRow);
199
            }
200
            
201
            // CSV
202
            $csv->output($fileName . '.csv');
203
            return true;
204
        }
205
        
206
        // XLSX
207
        if ($contentType === 'xlsx') {
208
            $xlsxArray = [];
209
            $xlsxArray [] = $listColumns;
210
            
211
            foreach ($list as $row) {
212
                $outputRow = [];
213
                foreach ($listColumns as $column) {
214
                    $outputRow[$column] = $row[$column] ?? '';
215
                }
216
                $xlsxArray [] = array_values($outputRow);
217
            }
218
            
219
            $xlsx = SimpleXLSXGen::fromArray($xlsxArray);
220
            return $xlsx->downloadAs($fileName . '.xlsx');
221
        }
222
        
223
        // Something went wrong
224
        throw new Exception('Failed to export `' . $this->getModelClassName() . '` using content-type `' . $contentType . '`', 400);
225
    }
226
}
227