Test Failed
Push — master ( 0de326...c1bf2c )
by Julien
04:42 queued 12s
created

Rest::flatternArrayForCsv()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 4
Bugs 1 Features 1
Metric Value
eloc 9
c 4
b 1
f 1
dl 0
loc 11
ccs 0
cts 10
cp 0
rs 8.8333
cc 7
nc 6
nop 1
crap 56
1
<?php
2
/**
3
 * This file is part of the Zemit Framework.
4
 *
5
 * (c) Zemit Team <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE.txt
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Zemit\Mvc\Controller;
12
13
use League\Csv\CharsetConverter;
14
use Phalcon\Http\Response;
15
use Phalcon\Mvc\Dispatcher;
16
use Phalcon\Mvc\Model\Resultset;
17
use League\Csv\Writer;
18
use Phalcon\Version;
19
use Zemit\Db\Profiler;
20
use Zemit\Utils\Slug;
21
22
/**
23
 * Class Rest
24
 *
25
 * @author Julien Turbide <[email protected]>
26
 * @copyright Zemit Team <[email protected]>
27
 *
28
 * @since 1.0
29
 * @version 1.0
30
 *
31
 *
32
 * @property Profiler $profiler
33
 * @package Zemit\Mvc\Controller
34
 */
35
class Rest extends \Zemit\Mvc\Controller
36
{
37
    use Model;
0 ignored issues
show
Bug introduced by
The trait Zemit\Mvc\Controller\Model requires the property $loader which is not provided by Zemit\Mvc\Controller\Rest.
Loading history...
38
    
39
    /**
40
     * Rest Bootstrap
41
     */
42
    public function indexAction($id = null)
43
    {
44
        $this->restForwarding($id);
45
    }
46
    
47
    /**
48
     * Rest bootstrap forwarding
49
     *
50
     * @return \Phalcon\Http\ResponseInterface
51
     */
52
    protected function restForwarding($id = null)
53
    {
54
        $id ??= $this->getParam('id');
55
        
56
        if ($this->request->isPost() || $this->request->isPut()) {
57
            $this->dispatcher->forward(['action' => 'save']);
58
        }
59
        else if ($this->request->isDelete()) {
60
            $this->dispatcher->forward(['action' => 'delete']);
61
        }
62
        else if ($this->request->isGet()) {
63
            if (is_null($id)) {
64
                $this->dispatcher->forward(['action' => 'getList']);
65
            }
66
            else {
67
                $this->dispatcher->forward(['action' => 'get']);
68
            }
69
        }
70
        else if ($this->request->isOptions()) {
71
            
72
            // @TODO review
73
            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#successful_responses
74
            // https://livebook.manning.com/book/cors-in-action/chapter-4/98
75
            return $this->setRestResponse(['result' => ''], 204, 'No Content');
76
        }
77
    }
78
    
79
    /**
80
     * Retrieving a single record
81
     * Alias of method getAction()
82
     * @deprecated Should use getAction() method instead
83
     *
84
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
85
     *
86
     * @return bool|\Phalcon\Http\ResponseInterface
87
     */
88
    public function getSingleAction($id = null) {
89
        return $this->getAction($id);
90
    }
91
    
92
    /**
93
     * Retrieving a single record
94
     *
95
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
96
     *
97
     * @return bool|\Phalcon\Http\ResponseInterface
98
     */
99
    public function getAction($id = null)
100
    {
101
        $modelName = $this->getModelClassName();
102
        $single = $this->getSingle($id, $modelName, null);
103
        
104
        $this->view->single = $single ? $single->expose($this->getExpose()) : false;
0 ignored issues
show
Bug introduced by
The method expose() does not exist on Phalcon\Mvc\Model\Resultset. ( Ignorable by Annotation )

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

104
        $this->view->single = $single ? $single->/** @scrutinizer ignore-call */ expose($this->getExpose()) : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Rest::getExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
105
        $this->view->model = $modelName;
106
        $this->view->source = $single ? $single->getSource() : false;
0 ignored issues
show
Bug introduced by
The method getSource() does not exist on Phalcon\Mvc\Model\Resultset. ( Ignorable by Annotation )

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

106
        $this->view->source = $single ? $single->/** @scrutinizer ignore-call */ getSource() : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
107
        
108
        if (!$single) {
109
            $this->response->setStatusCode(404, 'Not Found');
110
            
111
            return false;
112
        }
113
        
114
        return $this->setRestResponse();
115
    }
116
    
117
    /**
118
     * Retrieving a record list
119
     * Alias of method getListAction()
120
     * @deprecated Should use getListAction() method instead
121
     *
122
     * @return \Phalcon\Http\ResponseInterface
123
     */
124
    public function getAllAction() {
125
        return $this->getListAction();
126
    }
127
    
128
    /**
129
     * Retrieving a record list
130
     *
131
     * @return \Phalcon\Http\ResponseInterface
132
     * @throws \Exception
133
     */
134
    public function getListAction()
135
    {
136
        $model = $this->getModelClassName();
137
        
138
        /** @var Resultset $with */
139
        $find = $this->getFind();
140
        $with = $model::with($this->getListWith() ? : [], $find ? : []);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getListWith() targeting Zemit\Mvc\Controller\Rest::getListWith() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
141
        
142
        /**
143
         * Expose the list
144
         * @var int $key
145
         * @var \Zemit\Mvc\Model $item
146
         */
147
        $list = [];
148
        foreach ($with as $key => $item) {
149
            $list[$key] = $item->expose($this->getListExpose());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getListExpose() targeting Zemit\Mvc\Controller\Rest::getListExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
150
        }
151
        
152
        $list = is_array($list) ? array_values(array_filter($list)) : $list;
0 ignored issues
show
introduced by
The condition is_array($list) is always true.
Loading history...
153
        $this->view->list = $list;
154
        $this->view->listCount = count($list);
155
        $this->view->totalCount = $model::find($this->getFindCount($find));
156
        $this->view->totalCount = is_int($this->view->totalCount)? $this->view->totalCount : count($this->view->totalCount); // @todo fix count to work with rollup when joins
157
        $this->view->limit = $find['limit'] ?? false;
158
        $this->view->offset = $find['offset'] ?? false;
159
        $this->view->find = ($this->config->app->debug || $this->config->debug->enable) ? $find : false;
0 ignored issues
show
Bug Best Practice introduced by
The property debug does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property app does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
160
        
161
        return $this->setRestResponse();
162
    }
163
    
164
    /**
165
     * Exporting a record list into a CSV stream
166
     *
167
     * @return \Phalcon\Http\ResponseInterface
168
     * @throws \League\Csv\CannotInsertRecord
169
     * @throws \Zemit\Exception
170
     */
171
    public function exportAction()
172
    {
173
        $model = $this->getModelClassName();
174
        $params = $this->view->getParamsToView();
175
        $contentType = $this->getContentType();
176
        $fileName = ucfirst(Slug::generate(basename(str_replace('\\', '/', $this->getModelClassName())))) . ' List (' . date('Y-m-d') . ')';
177
        
178
        /** @var Resultset $with */
179
        $find = $this->getFind();
180
        $with = $model::with($this->getListWith() ? : [], $find ? : []);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getListWith() targeting Zemit\Mvc\Controller\Rest::getListWith() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
181
182
        /**
183
         * Expose the list
184
         * @var int $key
185
         * @var \Zemit\Mvc\Model $item
186
         */
187
        $list = [];
188
        foreach ($with as $key => $item) {
189
            $list[$key] = $item->expose($this->getExportExpose());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExportExpose() targeting Zemit\Mvc\Controller\Rest::getExportExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
190
        }
191
192
        $list = is_array($list) ? array_values(array_filter($list)) : $list;
0 ignored issues
show
introduced by
The condition is_array($list) is always true.
Loading history...
193
        $this->flatternArrayForCsv($list);
194
		$this->formatColumnText($list);
195
196
        if ($contentType === 'json') {
197
//            $this->response->setJsonContent($list);
198
            $this->response->setContent(json_encode($list, JSON_PRETTY_PRINT, 2048));
199
            $this->response->setContentType('application/json');
200
            $this->response->setHeader('Content-disposition', 'attachment; filename="'.addslashes($fileName).'.json"');
201
            return $this->response->send();
202
        }
203
        
204
        // CSV
205
        if ($contentType === 'csv') {
206
            
207
            // Get CSV custom request parameters
208
            $mode = $params['mode'] ?? null;
209
            $delimiter = $params['delimiter'] ?? null;
210
            $newline = $params['newline'] ?? null;
211
            $escape = $params['escape'] ?? null;
212
            $outputBOM = $params['outputBOM'] ?? null;
213
            $skipIncludeBOM = $params['skipIncludeBOM'] ?? null;
214
            
215
//            $csv = Writer::createFromFileObject(new \SplTempFileObject());
216
            $csv = Writer::createFromStream(fopen('php://memory', 'r+'));
217
            
218
            // CSV - MS Excel on MacOS
219
            if ($mode === 'mac') {
220
                $csv->setOutputBOM(Writer::BOM_UTF16_LE); // utf-16
221
                $csv->setDelimiter("\t"); // tabs separated
222
                $csv->setNewline("\r\n"); // new lines
223
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-16');
224
            }
225
            
226
            // CSV - MS Excel on Windows
227
            else {
228
                $csv->setOutputBOM(Writer::BOM_UTF8); // utf-8
229
                $csv->setDelimiter(','); // comma separated
230
                $csv->setNewline("\n"); // new line windows
231
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-8');
232
            }
233
            
234
            // Apply forced params from request
235
            if (isset($outputBOM)) {
236
                $csv->setOutputBOM($outputBOM);
237
            }
238
            if (isset($delimiter)) {
239
                $csv->setDelimiter($delimiter);
240
            }
241
            if (isset($newline)) {
242
                $csv->setNewline($newline);
243
            }
244
            if (isset($escape)) {
245
                $csv->setEscape($escape);
246
            }
247
            if ($skipIncludeBOM) {
248
                $csv->skipInputBOM();
249
            }
250
            else {
251
                $csv->includeInputBOM();
252
            }
253
            
254
            // CSV
255
            $csv->insertOne(array_keys($list[0]));
256
            $csv->insertAll($list);
257
            $csv->output($fileName . '.csv');
258
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
259
        }
260
        
261
        // XLSX
262
        if ($contentType === 'xlsx') {
263
            $xlsxArray = [];
264
            foreach ($list as $array) {
265
                if (empty($xlsxArray)) {
266
                    $xlsxArray []= array_keys($array);
267
                }
268
                $xlsxArray []= array_values($array);
269
            }
270
            $xlsx = \SimpleXLSXGen::fromArray($xlsxArray);
271
            $xlsx->downloadAs($fileName . '.xlsx');
272
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
273
        }
274
        
275
        // Something went wrong
276
        throw new \Exception('Failed to export `' . $this->getModelClassName() . '` using content-type `' . $contentType . '`', 400);
277
    }
278
279
    /**
280
     * @param array|null $array
281
     *
282
     * @return array|null
283
     */
284
    public function flatternArrayForCsv(?array &$list = null) {
285
286
        foreach ($list as $listKey => $listValue) {
287
            foreach ($listValue as $column => $value) {
288
                if (is_array($value) || is_object($value)) {
289
                    $value = $this->concatListFieldElementForCsv($value, ' ');
290
                    $list[$listKey][$column] = $this->arrayFlatten($value , $column);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object; however, parameter $array of Zemit\Mvc\Controller\Rest::arrayFlatten() does only seem to accept array|null, 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

290
                    $list[$listKey][$column] = $this->arrayFlatten(/** @scrutinizer ignore-type */ $value , $column);
Loading history...
Coding Style introduced by
Space found before comma in argument list
Loading history...
291
                    if (is_array($list[$listKey][$column])) {
292
                        foreach ($list[$listKey][$column] as $childKey => $childValue) {
293
                            $list[$listKey][$childKey] = $childValue;
294
                            unset ($list[$listKey][$column]);
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
295
                        }
296
                    }
297
                }
298
            }
299
        }
300
    }
301
302
    /**
303
     * @param array|object $list
304
     * @param string|null $seperator
305
     *
306
     * @return array|object
307
     */
308
    public function concatListFieldElementForCsv($list, $seperator = ' ') {
309
        foreach ($list as $valueKey => $element) {
310
            if (is_array($element) || is_object($element)) {
311
                $lastKey = array_key_last($list);
0 ignored issues
show
Bug introduced by
It seems like $list can also be of type object; however, parameter $array of array_key_last() does only seem to accept array, 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

311
                $lastKey = array_key_last(/** @scrutinizer ignore-type */ $list);
Loading history...
312
                if ($valueKey === $lastKey) {
313
                    continue;
314
                }
315
                foreach ($element as $elKey => $elValue) {
316
                    $list[$lastKey][$elKey] .= $seperator . $elValue;
317
                    if ($lastKey != $valueKey) {
318
                        unset($list[$valueKey]);
319
                    }
320
                }
321
            }
322
        }
323
324
        return $list;
325
    }
326
327
    /**
328
     * @param array|null $array
329
     * @param string|null $alias
330
     *
331
     * @return array|null
332
     */
333
    function arrayFlatten(?array $array, ?string $alias = null) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for arrayFlatten.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
334
        $return = array();
335
        foreach ($array as $key => $value) {
336
            if (is_array($value)) {
337
                $return = array_merge($return, $this->arrayFlatten($value, $alias));
338
            }
339
            else {
340
                $return[$alias . '.' . $key] = $value;
341
            }
342
        }
343
        return $return;
344
    }
345
346
    /**
347
     * @param array|null $listValue
348
     *
349
     * @return array|null
350
     */
351
    public function mergeColumns (?array $listValue) {
0 ignored issues
show
Coding Style introduced by
Expected "function abc(...)"; found "function abc (...)"
Loading history...
Coding Style introduced by
Expected 0 spaces before opening parenthesis; 1 found
Loading history...
352
        $columnToMergeList = $this->getExportMergeColum ();
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
Bug introduced by
Are you sure the assignment to $columnToMergeList is correct as $this->getExportMergeColum() targeting Zemit\Mvc\Controller\Rest::getExportMergeColum() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
353
        if (!$columnToMergeList || empty($columnToMergeList)) {
0 ignored issues
show
introduced by
$columnToMergeList is of type null, thus it always evaluated to false.
Loading history...
354
            return  $listValue;
355
        }
356
357
        $columnList = [];
358
        foreach ($columnToMergeList as $key => $columnToMerge) {
359
            foreach ($columnToMerge['columns'] as $column) {
360
                if (isset($listValue[$column])) {
361
                    $columnList[] = $listValue[$column];
362
                    unset($listValue[$column]);
363
                }
364
            }
365
            $listValue[$columnToMerge['name']] = implode (' ', $columnList ?? []);
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
366
        }
367
368
        return $listValue;
369
    }
370
371
    /**
372
     * @param array|null $list
373
     *
374
     * @return array|null
375
     */
376
    public function formatColumnText (?array &$list) {
0 ignored issues
show
Coding Style introduced by
Expected 0 spaces before opening parenthesis; 1 found
Loading history...
Coding Style introduced by
Expected "function abc(...)"; found "function abc (...)"
Loading history...
377
        foreach ($list as $listKey => $listValue) {
378
379
            $mergeColumArray = $this->mergeColumns($listValue);
380
            if(!empty($mergeColumArray)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space(s) after IF keyword; 0 found
Loading history...
381
                $list[$listKey] = $mergeColumArray;
382
            }
383
384
            $formatArray = $this->getExportFormatFieldText ($listValue);
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
Bug introduced by
Are you sure the assignment to $formatArray is correct as $this->getExportFormatFieldText($listValue) targeting Zemit\Mvc\Controller\Res...ExportFormatFieldText() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
385
            if ($formatArray) {
386
                $columNameList = array_keys($formatArray);
0 ignored issues
show
Bug introduced by
$formatArray of type void is incompatible with the type array expected by parameter $array of array_keys(). ( Ignorable by Annotation )

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

386
                $columNameList = array_keys(/** @scrutinizer ignore-type */ $formatArray);
Loading history...
387
                foreach ($formatArray as $formatKey => $formatValue) {
0 ignored issues
show
Bug introduced by
The expression $formatArray of type void is not traversable.
Loading history...
388
                    if (isset($formatValue['text'])) {
389
                        $list[$listKey][$formatKey] = $formatValue['text'];
390
                    }
391
392
                    if (isset($formatValue['rename'])) {
393
394
                        $list[$listKey][$formatValue['rename']] = $formatValue['text'] ?? ($list[$listKey][$formatKey] ?? null);
395
                        if ($formatValue['rename'] !== $formatKey) {
396
                            foreach ($columNameList as $columnKey => $columnValue) {
397
398
                                if ($formatKey === $columnValue) {
399
                                    $columNameList[$columnKey] = $formatValue['rename'];
400
                                }
401
                            }
402
403
                            unset($list[$listKey][$formatKey]);
404
                        }
405
                    }
406
                }
407
408
                if (isset($formatArray['reorderColumns']) && $formatArray['reorderColumns']) {
409
                    $list[$listKey] = $this->arrayCustomOrder($list[$listKey], $columNameList);
410
                }
411
            }
412
        }
413
414
        return $list;
415
    }
416
417
    /**
418
     * @param array $arrayToOrder
419
     * @param array $orderList
420
     *
421
     * @return array
422
     */
423
    function arrayCustomOrder($arrayToOrder, $orderList) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for arrayCustomOrder.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
424
        $ordered = array();
425
        foreach ($orderList as $key) {
426
            if (array_key_exists($key, $arrayToOrder)) {
427
                $ordered[$key] = $arrayToOrder[$key];
428
            }
429
        }
430
        return $ordered;
431
    }
432
433
    /**
434
     * Count a record list
435
     * @TODO add total count / deleted count / active count
436
     *
437
     * @return \Phalcon\Http\ResponseInterface
438
     */
439
    public function countAction()
440
    {
441
        $model = $this->getModelClassName();
442
        
443
        /** @var \Zemit\Mvc\Model $entity */
444
        $entity = new $model();
445
        
446
        $this->view->totalCount = $model::count($this->getFindCount($this->getFind()));
447
        $this->view->totalCount = is_int($this->view->totalCount)? $this->view->totalCount : count($this->view->totalCount);
448
        $this->view->model = get_class($entity);
449
        $this->view->source = $entity->getSource();
450
        
451
        return $this->setRestResponse();
452
    }
453
    
454
    /**
455
     * Prepare a new model for the frontend
456
     *
457
     * @return \Phalcon\Http\ResponseInterface
458
     */
459
    public function newAction()
460
    {
461
        $model = $this->getModelClassName();
462
        
463
        /** @var \Zemit\Mvc\Model $entity */
464
        $entity = new $model();
465
        $entity->assign($this->getParams(), $this->getWhiteList(), $this->getColumnMap());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getColumnMap() targeting Zemit\Mvc\Controller\Rest::getColumnMap() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure the usage of $this->getWhiteList() targeting Zemit\Mvc\Controller\Rest::getWhiteList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
466
        
467
        $this->view->model = get_class($entity);
468
        $this->view->source = $entity->getSource();
469
        $this->view->single = $entity->expose($this->getExpose());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Rest::getExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
470
        
471
        return $this->setRestResponse();
472
    }
473
    
474
    /**
475
     * Prepare a new model for the frontend
476
     *
477
     * @return \Phalcon\Http\ResponseInterface
478
     */
479
    public function validateAction($id = null)
480
    {
481
        $model = $this->getModelClassName();
482
        
483
        /** @var \Zemit\Mvc\Model $entity */
484
        $entity = $this->getSingle($id);
485
        $new = !$entity;
0 ignored issues
show
introduced by
$entity is of type Zemit\Mvc\Model, thus it always evaluated to true.
Loading history...
486
        
487
        if ($new) {
0 ignored issues
show
introduced by
The condition $new is always false.
Loading history...
488
            $entity = new $model();
489
        }
490
        
491
        $entity->assign($this->getParams(), $this->getWhiteList(), $this->getColumnMap());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWhiteList() targeting Zemit\Mvc\Controller\Rest::getWhiteList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure the usage of $this->getColumnMap() targeting Zemit\Mvc\Controller\Rest::getColumnMap() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
492
        
493
        /**
494
         * Event to run
495
         * @see https://docs.phalcon.io/4.0/en/db-models-events
496
         */
497
        $events = [
498
            'beforeCreate' => null,
499
            'beforeUpdate' => null,
500
            'beforeSave' => null,
501
            'beforeValidationOnCreate' => null,
502
            'beforeValidationOnUpdate' => null,
503
            'beforeValidation' => null,
504
            'prepareSave' => null,
505
            'validation' => null,
506
            'afterValidationOnCreate' => null,
507
            'afterValidationOnUpdate' => null,
508
            'afterValidation' => null,
509
        ];
510
        
511
        // run events, as it would normally
512
        foreach ($events as $event => $state) {
513
            $this->skipped = false;
0 ignored issues
show
Bug Best Practice introduced by
The property skipped does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
514
            
515
            // skip depending wether it's a create or update
516
            if (strpos($event, $new ? 'Update' : 'Create') !== false) {
517
                continue;
518
            }
519
            
520
            // fire the event, allowing to fail or skip
521
            $events[$event] = $entity->fireEventCancel($event);
522
            if ($events[$event] === false) {
523
                // event failed
524
                break;
525
            }
526
            
527
            // event was skipped, just for consistencies purpose
528
            if ($this->skipped) {
529
                continue;
530
            }
531
        }
532
        
533
        $this->view->model = get_class($entity);
534
        $this->view->source = $entity->getSource();
535
        $this->view->single = $entity->expose($this->getExpose());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Rest::getExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
536
        $this->view->messages = $entity->getMessages();
537
        $this->view->events = $events;
538
        $this->view->validated = empty($this->view->messages);
539
        
540
        return $this->setRestResponse($this->view->validated);
0 ignored issues
show
Bug introduced by
$this->view->validated of type boolean is incompatible with the type array|null expected by parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse(). ( Ignorable by Annotation )

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

540
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->validated);
Loading history...
541
    }
542
    
543
    /**
544
     * Saving a record
545
     * - Create
546
     * - Update
547
     *
548
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
549
     *
550
     * @return array
551
     */
552
    public function saveAction($id = null)
553
    {
554
        $this->view->setVars($this->save($id));
0 ignored issues
show
Bug introduced by
The method setVars() does not exist on Phalcon\Mvc\ViewInterface. Did you maybe mean setVar()? ( Ignorable by Annotation )

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

554
        $this->view->/** @scrutinizer ignore-call */ 
555
                     setVars($this->save($id));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
555
        
556
        return $this->setRestResponse($this->view->saved);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setRestResponse($this->view->saved) returns the type Phalcon\Http\ResponseInterface which is incompatible with the documented return type array.
Loading history...
557
    }
558
    
559
    /**
560
     * Deleting a record
561
     *
562
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
563
     *
564
     * @return bool
565
     */
566
    public function deleteAction($id = null)
567
    {
568
        $single = $this->getSingle($id);
569
        
570
        $this->view->deleted = $single ? $single->delete() : false;
571
        $this->view->single = $single ? $single->expose($this->getExpose()) : false;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Rest::getExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
572
        $this->view->messages = $single ? $single->getMessages() : false;
573
        
574
        if (!$single) {
575
            $this->response->setStatusCode(404, 'Not Found');
576
            
577
            return false;
578
        }
579
        
580
        return $this->setRestResponse($this->view->deleted);
0 ignored issues
show
Bug introduced by
$this->view->deleted of type boolean is incompatible with the type array|null expected by parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse(). ( Ignorable by Annotation )

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

580
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->deleted);
Loading history...
Bug Best Practice introduced by
The expression return $this->setRestRes...e($this->view->deleted) returns the type Phalcon\Http\ResponseInterface which is incompatible with the documented return type boolean.
Loading history...
581
    }
582
    
583
    /**
584
     * Restoring record
585
     *
586
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
587
     *
588
     * @return bool
589
     */
590
    public function restoreAction($id = null)
591
    {
592
        $single = $this->getSingle($id);
593
        
594
        $this->view->restored = $single ? $single->restore() : false;
0 ignored issues
show
Bug introduced by
The method restore() does not exist on Phalcon\Mvc\Model\Resultset. ( Ignorable by Annotation )

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

594
        $this->view->restored = $single ? $single->/** @scrutinizer ignore-call */ restore() : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
595
        $this->view->single = $single ? $single->expose($this->getExpose()) : false;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Rest::getExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
596
        $this->view->messages = $single ? $single->getMessages() : false;
597
        
598
        if (!$single) {
599
            $this->response->setStatusCode(404, 'Not Found');
600
            
601
            return false;
602
        }
603
        
604
        return $this->setRestResponse($this->view->restored);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->setRestRes...($this->view->restored) returns the type Phalcon\Http\ResponseInterface which is incompatible with the documented return type boolean.
Loading history...
Bug introduced by
It seems like $this->view->restored can also be of type false; however, parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse() does only seem to accept array|null, 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

604
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->restored);
Loading history...
605
    }
606
    
607
    /**
608
     * Re-ordering a position
609
     *
610
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
611
     * @param null $position
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $position is correct as it would always require null to be passed?
Loading history...
612
     *
613
     * @return bool|\Phalcon\Http\ResponseInterface
614
     */
615
    public function reorderAction($id = null)
616
    {
617
        $single = $this->getSingle($id);
618
        
619
        $position = $this->getParam('position', 'int');
620
        
621
        $this->view->reordered = $single ? $single->reorder($position) : false;
0 ignored issues
show
Bug introduced by
The method reorder() does not exist on Phalcon\Mvc\Model\Resultset. ( Ignorable by Annotation )

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

621
        $this->view->reordered = $single ? $single->/** @scrutinizer ignore-call */ reorder($position) : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
622
        $this->view->single = $single ? $single->expose($this->getExpose()) : false;
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Rest::getExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
623
        $this->view->messages = $single ? $single->getMessages() : false;
624
        
625
        if (!$single) {
626
            $this->response->setStatusCode(404, 'Not Found');
627
            
628
            return false;
629
        }
630
        
631
        return $this->setRestResponse($this->view->reordered);
0 ignored issues
show
Bug introduced by
It seems like $this->view->reordered can also be of type false; however, parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse() does only seem to accept array|null, 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

631
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->reordered);
Loading history...
632
    }
633
    
634
    /**
635
     * Sending an error as an http response
636
     *
637
     * @param null $error
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $error is correct as it would always require null to be passed?
Loading history...
638
     * @param null $response
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $response is correct as it would always require null to be passed?
Loading history...
639
     *
640
     * @return \Phalcon\Http\ResponseInterface
641
     */
642
    public function setRestErrorResponse($code = 400, $status = 'Bad Request', $response = null)
643
    {
644
        return $this->setRestResponse($response, $code, $status);
645
    }
646
    
647
    /**
648
     * Sending rest response as an http response
649
     *
650
     * @param array|null $response
651
     * @param null $status
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $status is correct as it would always require null to be passed?
Loading history...
652
     * @param null $code
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $code is correct as it would always require null to be passed?
Loading history...
653
     * @param int $jsonOptions
654
     * @param int $depth
655
     *
656
     * @return \Phalcon\Http\ResponseInterface
657
     */
658
    public function setRestResponse($response = null, $code = null, $status = null, $jsonOptions = 0, $depth = 512)
659
    {
660
        $debug = $this->config->app->debug ?? false;
0 ignored issues
show
Bug Best Practice introduced by
The property app does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
661
        
662
        // keep forced status code or set our own
663
        $responseStatusCode = $this->response->getStatusCode();
664
        $reasonPhrase = $this->response->getReasonPhrase();
665
        $status ??= $reasonPhrase ? : 'OK';
666
        $code ??= (int)$responseStatusCode ? : 200;
667
        $view = $this->view->getParamsToView();
668
        $hash = hash('sha512', json_encode($view));
669
        
670
        /**
671
         * Debug section
672
         * - Versions
673
         * - Request
674
         * - Identity
675
         * - Profiler
676
         * - Dispatcher
677
         * - Router
678
         */
679
        $request = $debug ? $this->request->toArray() : null;
0 ignored issues
show
introduced by
The method toArray() does not exist on Phalcon\Http\Request. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

679
        $request = $debug ? $this->request->/** @scrutinizer ignore-call */ toArray() : null;
Loading history...
Bug introduced by
The method toArray() does not exist on Phalcon\Http\RequestInterface. It seems like you code against a sub-type of Phalcon\Http\RequestInterface such as Zemit\Http\Request. ( Ignorable by Annotation )

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

679
        $request = $debug ? $this->request->/** @scrutinizer ignore-call */ toArray() : null;
Loading history...
680
        $identity = $debug ? $this->identity->getIdentity() : null;
681
        $profiler = $debug && $this->profiler ? $this->profiler->toArray() : null;
682
        $dispatcher = $debug ? $this->dispatcher->toArray() : null;
683
        $router = $debug ? $this->router->toArray() : null;
684
        
685
        $api = $debug ? [
686
            'php' => phpversion(),
687
            'phalcon' => Version::get(),
688
            'zemit' => $this->config->core->version,
0 ignored issues
show
Bug Best Practice introduced by
The property core does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
689
            'core' => $this->config->core->name,
690
            'app' => $this->config->app->version,
691
            'name' => $this->config->app->name,
692
        ] : [];
693
        $api['version'] = '0.1';
694
        
695
        $this->response->setStatusCode($code, $code . ' ' . $status);
696
        
697
        // @todo handle this correctly
698
        // @todo private vs public cache type
699
        $cache = $this->getCache();
700
        if (!empty($cache['lifetime'])) {
701
            if ($this->response->getStatusCode() === 200) {
702
                $this->response->setCache($cache['lifetime']);
703
                $this->response->setEtag($hash);
704
            }
705
        } else {
706
            $this->response->setCache(0);
707
            $this->response->setHeader('Cache-Control', 'no-cache, max-age=0');
708
        }
709
        
710
        return $this->response->setJsonContent(array_merge([
711
            'api' => $api,
712
            'timestamp' => date('c'),
713
            'hash' => $hash,
714
            'status' => $status,
715
            'code' => $code,
716
            'response' => $response,
717
            'view' => $view,
718
        ], $debug ? [
719
            'identity' => $identity,
720
            'profiler' => $profiler,
721
            'request' => $request,
722
            'dispatcher' => $dispatcher,
723
            'router' => $router,
724
        ] : []), $jsonOptions, $depth);
0 ignored issues
show
Unused Code introduced by
The call to Phalcon\Http\ResponseInterface::setJsonContent() has too many arguments starting with $jsonOptions. ( Ignorable by Annotation )

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

724
        return $this->response->/** @scrutinizer ignore-call */ setJsonContent(array_merge([

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
725
    }
726
    
727
    /**
728
     * Handle rest response automagically
729
     *
730
     * @param Dispatcher $dispatcher
731
     */
732
    public function afterExecuteRoute(Dispatcher $dispatcher)
733
    {
734
        $response = $dispatcher->getReturnedValue();
735
        
736
        // Avoid breaking default phalcon behaviour
737
        if ($response instanceof Response) {
738
            return;
739
        }
740
        
741
        // Merge response into view variables
742
        if (is_array($response)) {
743
            $this->view->setVars($response, true);
744
        }
745
        
746
        // Return our Rest normalized response
747
        $dispatcher->setReturnedValue($this->setRestResponse(is_array($response) ? null : $response));
748
    }
749
}
750