ExportsCollection::toScalar()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
namespace Terranet\Administrator\Traits;
4
5
use Carbon\Carbon;
6
use DOMDocument;
7
use Generator;
8
use Illuminate\Database\Eloquent\Builder;
0 ignored issues
show
Bug introduced by
The type Illuminate\Database\Eloquent\Builder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
9
use Illuminate\Database\Eloquent\Model;
0 ignored issues
show
Bug introduced by
The type Illuminate\Database\Eloquent\Model was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
10
use Illuminate\Support\Collection;
11
use Terranet\Administrator\Architect;
12
use Terranet\Administrator\Exception;
13
use Terranet\Translatable\Translatable;
14
15
trait ExportsCollection
16
{
17
    /**
18
     * Export collection to a specific format.
19
     *
20
     * @param Builder $query
21
     * @param         $format
22
     *
23
     * @throws Exception
24
     *
25
     * @return mixed
26
     */
27
    public function export(Builder $query, $format)
28
    {
29
        $method = 'to'.strtoupper($format);
30
31
        if (method_exists($this, $method)) {
32
            return \call_user_func_array([$this, $method], [$query]);
33
        }
34
35
        if (method_exists($this->module, $method)) {
36
            return \call_user_func_array([$this->module, $method], [$query]);
37
        }
38
39
        throw new Exception(sprintf('Don\'t know how to export to %s format.', $format));
40
    }
41
42
    /**
43
     * Convert & download collection in JSON format.
44
     *
45
     * @param $query
46
     *
47
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
48
     */
49
    public function toJSON(Builder $query)
50
    {
51
        file_put_contents(
52
            $file = $this->getFilename(),
53
            json_encode($this->exportableQuery($query)->get())
54
        );
55
56
        return $this->sendDownloadResponse($file, 'json', ['Content-Type' => 'application/json']);
57
    }
58
59
    /**
60
     * Convert & download collection in XML format.
61
     *
62
     * @param $query
63
     *
64
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
65
     */
66
    public function toXML(Builder $query)
67
    {
68
        $root = with($dom = new DOMDocument())->createElement('root');
69
70
        foreach ($this->each($query) as $object) {
71
            $item = $dom->createElement('item');
72
73
            foreach ($this->toScalar($object) as $column => $value) {
74
                $column = $dom->createElement($column, htmlspecialchars($value));
75
                $item->appendChild($column);
76
            }
77
78
            $root->appendChild($item);
79
        }
80
81
        $dom->appendChild($root);
82
83
        file_put_contents(
84
            $file = $this->getFilename(),
85
            $dom->saveXML()
86
        );
87
88
        return $this->sendDownloadResponse($file, 'xml', ['Content-Type' => 'text/xml']);
89
    }
90
91
    /**
92
     * Convert & download collection in CSV format.
93
     *
94
     * @param $query
95
     *
96
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
97
     */
98
    public function toCSV(Builder $query)
99
    {
100
        $out = fopen(
101
            $file = $this->getFilename(),
102
            'a+b'
103
        );
104
        fputs($out, $bom = (chr(0xEF).chr(0xBB).chr(0xBF)));
0 ignored issues
show
Bug introduced by
It seems like $out can also be of type false; however, parameter $handle of fputs() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

104
        fputs(/** @scrutinizer ignore-type */ $out, $bom = (chr(0xEF).chr(0xBB).chr(0xBF)));
Loading history...
105
        
106
        $headersPrinted = false;
107
108
        foreach ($this->each($query, 100) as $item) {
109
            $data = $this->toScalar($item);
110
111
            if (!$headersPrinted) {
112
                fputcsv($out, array_keys($data));
0 ignored issues
show
Bug introduced by
It seems like $out can also be of type false; however, parameter $handle of fputcsv() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

112
                fputcsv(/** @scrutinizer ignore-type */ $out, array_keys($data));
Loading history...
113
                $headersPrinted = true;
114
            }
115
116
            fputcsv($out, $data);
117
        }
118
        fclose($out);
0 ignored issues
show
Bug introduced by
It seems like $out can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

118
        fclose(/** @scrutinizer ignore-type */ $out);
Loading history...
119
120
        return $this->sendDownloadResponse($file, 'csv', ['Content-Type' => 'text/csv']);
121
    }
122
123
    /**
124
     * @param Builder $query
125
     *
126
     * @throws Exception
127
     * @throws \Throwable
128
     *
129
     * @return mixed
130
     */
131
    public function toPDF(Builder $query)
132
    {
133
        if (!app()->has('dompdf.wrapper')) {
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

133
        if (!/** @scrutinizer ignore-call */ app()->has('dompdf.wrapper')) {
Loading history...
134
            throw new Exception(sprintf(
135
                "'%s' package required to generate PDF documents.",
136
                'barryvdh/laravel-dompdf'
137
            ));
138
        }
139
140
        $pdf = app('dompdf.wrapper');
141
        $view = method_exists($this->module, 'exportableView')
142
            ? $this->module->exportableView()
143
            : $this->exportableView();
144
145
        $html = view($view, [
0 ignored issues
show
Bug introduced by
The function view was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

145
        $html = /** @scrutinizer ignore-call */ view($view, [
Loading history...
146
            'module' => app('scaffold.module')->url(),
147
            'time' => new Carbon(),
148
            'items' => $this->each($query, 100),
149
        ])->render();
150
151
        return $pdf->loadHTML($html)
152
            ->setPaper('a4', 'landscape')
153
            ->download(app('scaffold.module')->url().'.pdf');
154
    }
155
156
    /**
157
     * Fetch exportable items by query.
158
     *
159
     * @param Builder $query
160
     *
161
     * @return Builder
162
     */
163
    protected function exportableQuery(Builder $query): Builder
164
    {
165
        // Allow executing custom exportable query.
166
        if (method_exists($this->module, 'exportableQuery')) {
167
            return $this->module->exportableQuery($query);
168
        }
169
170
        return $query
171
            ->when($query->getModel() instanceof Translatable, function ($query) {
172
                $query->translated();
173
            })
174
            // leave select after joining with translations
175
            // table in order to rewrite selected columns
176
            ->select($this->exportableColumns());
177
    }
178
179
    /**
180
     * @param $object
181
     *
182
     * @return array
183
     */
184
    protected function toScalar($object): array
185
    {
186
        return array_filter($object->toArray(), function ($item) {
187
            return is_scalar($item);
188
        });
189
    }
190
191
    /**
192
     * @return string
193
     */
194
    protected function getFilename(): string
195
    {
196
        return tempnam(sys_get_temp_dir(), app('scaffold.module')->url().'_');
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

196
        return tempnam(sys_get_temp_dir(), /** @scrutinizer ignore-call */ app('scaffold.module')->url().'_');
Loading history...
197
    }
198
199
    /**
200
     * @param $file
201
     * @param $extension
202
     * @param array $headers
203
     *
204
     * @return mixed
205
     */
206
    protected function sendDownloadResponse($file, $extension, array $headers = [])
207
    {
208
        return response()->download(
0 ignored issues
show
Bug introduced by
The function response was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

208
        return /** @scrutinizer ignore-call */ response()->download(
Loading history...
209
            $file,
210
            app('scaffold.module')->title().'.'.$extension,
0 ignored issues
show
Bug introduced by
The function app was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

210
            /** @scrutinizer ignore-call */ 
211
            app('scaffold.module')->title().'.'.$extension,
Loading history...
211
            $headers
212
        );
213
    }
214
215
    /**
216
     * Creates a all items generator.
217
     *
218
     * @param Builder $query
219
     * @param int $count
220
     *
221
     * @return Generator
222
     */
223
    protected function each(Builder $query, $count = 100): Generator
224
    {
225
        $query = $this->exportableQuery($query);
226
227
        // enforce order by statement.
228
        if (empty($query->orders) && empty($query->unionOrders)) {
229
            $query->orderBy($query->getModel()->getQualifiedKeyName(), 'asc');
230
        }
231
232
        $page = 1;
233
234
        do {
235
            // We'll execute the query for the given page and get the results. If there are
236
            // no results we can just break and return from here. When there are results
237
            // we will call the callback with the current chunk of these results here.
238
            $results = $query->forPage($page, $count)->get();
239
240
            $countResults = $results->count();
241
242
            if (0 === $countResults) {
243
                break;
244
            }
245
246
            // On each chunk result set, we will pass them to the callback and then let the
247
            // developer take care of everything within the callback, which allows us to
248
            // keep the memory low for spinning through large result sets for working.
249
            foreach ($results as $index => $item) {
250
                yield $index = $item;
0 ignored issues
show
Unused Code introduced by
The assignment to $index is dead and can be removed.
Loading history...
251
            }
252
253
            unset($results);
254
255
            ++$page;
256
        } while ($countResults === $count);
257
    }
258
259
    /**
260
     * Generate a list of exportable columns.
261
     *
262
     * @return array
263
     */
264
    protected function exportableColumns(): array
265
    {
266
        if (method_exists($this->module, 'exportableColumns')) {
267
            return $this->module->exportableColumns();
268
        }
269
270
        /**
271
         * @var Model
272
         */
273
        $model = $this->module->model();
274
275
        return collect($model->getFillable())
276
            ->prepend('id')
277
            ->diff($model->getHidden())
278
            ->map(function ($column) use ($model) {
279
                return "{$model->getTable()}.{$column}";
280
            })
281
            ->when($model instanceof Translatable, function (Collection $collection) use ($model) {
282
                return $collection->merge(
283
                    collect($model->getTranslatedAttributes())
284
                        ->map(function ($column) use ($model) {
0 ignored issues
show
Unused Code introduced by
The import $model is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
285
                            return "tt.{$column}";
286
                        })
287
                );
288
            })
289
            ->all();
290
    }
291
292
    /**
293
     * @return mixed
294
     */
295
    protected function exportableView()
296
    {
297
        return Architect::template()->layout('exportable');
298
    }
299
}
300