Test Failed
Push — master ( 4abc3b...a9eb80 )
by Julien
04:29
created

Rest::attachBehaviors()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 10
ccs 0
cts 8
cp 0
rs 10
cc 4
nc 6
nop 2
crap 20
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\Events\Manager;
15
use Phalcon\Http\Response;
16
use Phalcon\Mvc\Dispatcher;
17
use Phalcon\Mvc\Model\Resultset;
18
use League\Csv\Writer;
19
use Phalcon\Version;
20
use Zemit\Db\Profiler;
21
use Zemit\Di\Injectable;
22
use Zemit\Utils\Slug;
23
24
/**
25
 * Class Rest
26
 *
27
 * @author Julien Turbide <[email protected]>
28
 * @copyright Zemit Team <[email protected]>
29
 *
30
 * @since 1.0
31
 * @version 1.0
32
 *
33
 *
34
 * @property Profiler $profiler
35
 * @package Zemit\Mvc\Controller
36
 */
37
class Rest extends \Zemit\Mvc\Controller
38
{
39
    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...
40
    
41
    /**
42
     * Rest Bootstrap
43
     */
44
    public function indexAction($id = null)
45
    {
46
        $this->restForwarding($id);
47
    }
48
    
49
    /**
50
     * Rest bootstrap forwarding
51
     *
52
     * @return \Phalcon\Http\ResponseInterface
53
     */
54
    protected function restForwarding($id = null)
55
    {
56
        $id ??= $this->getParam('id');
57
        
58
        if ($this->request->isPost() || $this->request->isPut()) {
59
            $this->dispatcher->forward(['action' => 'save']);
60
        }
61
        else if ($this->request->isDelete()) {
62
            $this->dispatcher->forward(['action' => 'delete']);
63
        }
64
        else if ($this->request->isGet()) {
65
            if (is_null($id)) {
66
                $this->dispatcher->forward(['action' => 'getList']);
67
            }
68
            else {
69
                $this->dispatcher->forward(['action' => 'get']);
70
            }
71
        }
72
        else if ($this->request->isOptions()) {
73
            
74
            // @TODO review
75
            // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status#successful_responses
76
            // https://livebook.manning.com/book/cors-in-action/chapter-4/98
77
            return $this->setRestResponse(['result' => ''], 204, 'No Content');
78
        }
79
    }
80
    
81
    /**
82
     * Retrieving a single record
83
     * Alias of method getAction()
84
     * @deprecated Should use getAction() method instead
85
     *
86
     * @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...
87
     *
88
     * @return bool|\Phalcon\Http\ResponseInterface
89
     */
90
    public function getSingleAction($id = null) {
91
        return $this->getAction($id);
92
    }
93
    
94
    /**
95
     * Retrieving a single record
96
     *
97
     * @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...
98
     *
99
     * @return bool|\Phalcon\Http\ResponseInterface
100
     */
101
    public function getAction($id = null)
102
    {
103
        $modelName = $this->getModelClassName();
104
        $single = $this->getSingle($id, $modelName, null);
105
        
106
        $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

106
        $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...
107
        $this->view->model = $modelName;
108
        $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

108
        $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...
109
        
110
        if (!$single) {
111
            $this->response->setStatusCode(404, 'Not Found');
112
            
113
            return false;
114
        }
115
        
116
        return $this->setRestResponse();
117
    }
118
    
119
    /**
120
     * Retrieving a record list
121
     * Alias of method getListAction()
122
     * @deprecated Should use getListAction() method instead
123
     *
124
     * @return \Phalcon\Http\ResponseInterface
125
     */
126
    public function getAllAction() {
127
        return $this->getListAction();
128
    }
129
    
130
    /**
131
     * Retrieving a record list
132
     *
133
     * @return \Phalcon\Http\ResponseInterface
134
     * @throws \Exception
135
     */
136
    public function getListAction()
137
    {
138
        $model = $this->getModelClassName();
139
        
140
        /** @var Resultset $with */
141
        $find = $this->getFind();
142
        $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...
143
        
144
        /**
145
         * Expose the list
146
         * @var int $key
147
         * @var \Zemit\Mvc\Model $item
148
         */
149
        $list = [];
150
        foreach ($with as $key => $item) {
151
            $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...
152
        }
153
        
154
        $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...
155
        $this->view->list = $list;
156
        $this->view->listCount = count($list);
157
        $this->view->totalCount = $model::find($this->getFindCount($find));
158
        $this->view->totalCount = is_int($this->view->totalCount)? $this->view->totalCount : count($this->view->totalCount); // @todo fix count to work with rollup when joins
159
        $this->view->limit = $find['limit'] ?? false;
160
        $this->view->offset = $find['offset'] ?? false;
161
        $this->view->find = ($this->config->app->debug || $this->config->debug->enable) ? $find : 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...
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...
162
        
163
        return $this->setRestResponse();
164
    }
165
    
166
    /**
167
     * Exporting a record list into a CSV stream
168
     *
169
     * @return \Phalcon\Http\ResponseInterface
170
     * @throws \League\Csv\CannotInsertRecord
171
     * @throws \Zemit\Exception
172
     */
173
    public function exportAction()
174
    {
175
        $model = $this->getModelClassName();
176
        $params = $this->view->getParamsToView();
177
        $contentType = $this->getContentType();
178
        $fileName = ucfirst(Slug::generate(basename(str_replace('\\', '/', $this->getModelClassName())))) . ' List (' . date('Y-m-d') . ')';
179
        
180
        /** @var Resultset $with */
181
        $find = $this->getFind();
182
        $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...
183
184
        /**
185
         * Expose the list
186
         * @var int $key
187
         * @var \Zemit\Mvc\Model $item
188
         */
189
        $list = [];
190
        foreach ($with as $key => $item) {
191
            $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...
192
        }
193
194
        $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...
195
        $this->flatternArrayForCsv($list);
196
		$this->formatColumnText($list);
197
198
        if ($contentType === 'json') {
199
//            $this->response->setJsonContent($list);
200
            $this->response->setContent(json_encode($list, JSON_PRETTY_PRINT, 2048));
201
            $this->response->setContentType('application/json');
202
            $this->response->setHeader('Content-disposition', 'attachment; filename="'.addslashes($fileName).'.json"');
203
            return $this->response->send();
204
        }
205
        
206
        // CSV
207
        if ($contentType === 'csv') {
208
            
209
            // Get CSV custom request parameters
210
            $mode = $params['mode'] ?? null;
211
            $delimiter = $params['delimiter'] ?? null;
212
            $newline = $params['newline'] ?? null;
213
            $escape = $params['escape'] ?? null;
214
            $outputBOM = $params['outputBOM'] ?? null;
215
            $skipIncludeBOM = $params['skipIncludeBOM'] ?? null;
216
            
217
//            $csv = Writer::createFromFileObject(new \SplTempFileObject());
218
            $csv = Writer::createFromStream(fopen('php://memory', 'r+'));
219
            
220
            // CSV - MS Excel on MacOS
221
            if ($mode === 'mac') {
222
                $csv->setOutputBOM(Writer::BOM_UTF16_LE); // utf-16
223
                $csv->setDelimiter("\t"); // tabs separated
224
                $csv->setNewline("\r\n"); // new lines
225
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-16');
226
            }
227
            
228
            // CSV - MS Excel on Windows
229
            else {
230
                $csv->setOutputBOM(Writer::BOM_UTF8); // utf-8
231
                $csv->setDelimiter(','); // comma separated
232
                $csv->setNewline("\n"); // new line windows
233
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-8');
234
            }
235
            
236
            // Apply forced params from request
237
            if (isset($outputBOM)) {
238
                $csv->setOutputBOM($outputBOM);
239
            }
240
            if (isset($delimiter)) {
241
                $csv->setDelimiter($delimiter);
242
            }
243
            if (isset($newline)) {
244
                $csv->setNewline($newline);
245
            }
246
            if (isset($escape)) {
247
                $csv->setEscape($escape);
248
            }
249
            if ($skipIncludeBOM) {
250
                $csv->skipInputBOM();
251
            }
252
            else {
253
                $csv->includeInputBOM();
254
            }
255
            
256
            // CSV
257
            $csv->insertOne(array_keys($list[0]));
258
            $csv->insertAll($list);
259
            $csv->output($fileName . '.csv');
260
            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...
261
        }
262
        
263
        // XLSX
264
        if ($contentType === 'xlsx') {
265
            $xlsxArray = [];
266
            foreach ($list as $array) {
267
                if (empty($xlsxArray)) {
268
                    $xlsxArray []= array_keys($array);
269
                }
270
                $xlsxArray []= array_values($array);
271
            }
272
            $xlsx = \SimpleXLSXGen::fromArray($xlsxArray);
273
            $xlsx->downloadAs($fileName . '.xlsx');
274
            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...
275
        }
276
        
277
        // Something went wrong
278
        throw new \Exception('Failed to export `' . $this->getModelClassName() . '` using content-type `' . $contentType . '`', 400);
279
    }
280
281
    /**
282
     * @param array|null $array
283
     *
284
     * @return array|null
285
     */
286
    public function flatternArrayForCsv(?array &$list = null) {
287
288
        foreach ($list as $listKey => $listValue) {
289
            foreach ($listValue as $column => $value) {
290
                if (is_array($value) || is_object($value)) {
291
                    $value = $this->concatListFieldElementForCsv($value, ' ');
292
                    $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

292
                    $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...
293
                    if (is_array($list[$listKey][$column])) {
294
                        foreach ($list[$listKey][$column] as $childKey => $childValue) {
295
                            $list[$listKey][$childKey] = $childValue;
296
                            unset ($list[$listKey][$column]);
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
297
                        }
298
                    }
299
                }
300
            }
301
        }
302
    }
303
304
    /**
305
     * @param array|object $list
306
     * @param string|null $seperator
307
     *
308
     * @return array|object
309
     */
310
    public function concatListFieldElementForCsv($list, $seperator = ' ') {
311
        foreach ($list as $valueKey => $element) {
312
            if (is_array($element) || is_object($element)) {
313
                $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

313
                $lastKey = array_key_last(/** @scrutinizer ignore-type */ $list);
Loading history...
314
                if ($valueKey === $lastKey) {
315
                    continue;
316
                }
317
                foreach ($element as $elKey => $elValue) {
318
                    $list[$lastKey][$elKey] .= $seperator . $elValue;
319
                    if ($lastKey != $valueKey) {
320
                        unset($list[$valueKey]);
321
                    }
322
                }
323
            }
324
        }
325
326
        return $list;
327
    }
328
329
    /**
330
     * @param array|null $array
331
     * @param string|null $alias
332
     *
333
     * @return array|null
334
     */
335
    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...
336
        $return = array();
337
        foreach ($array as $key => $value) {
338
            if (is_array($value)) {
339
                $return = array_merge($return, $this->arrayFlatten($value, $alias));
340
            }
341
            else {
342
                $return[$alias . '.' . $key] = $value;
343
            }
344
        }
345
        return $return;
346
    }
347
348
    /**
349
     * @param array|null $listValue
350
     *
351
     * @return array|null
352
     */
353
    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...
354
        $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...
355
        if (!$columnToMergeList || empty($columnToMergeList)) {
0 ignored issues
show
introduced by
$columnToMergeList is of type null, thus it always evaluated to false.
Loading history...
356
            return  $listValue;
357
        }
358
359
        $columnList = [];
360
        foreach ($columnToMergeList as $key => $columnToMerge) {
361
            foreach ($columnToMerge['columns'] as $column) {
362
                if (isset($listValue[$column])) {
363
                    $columnList[] = $listValue[$column];
364
                    unset($listValue[$column]);
365
                }
366
            }
367
            $listValue[$columnToMerge['name']] = implode (' ', $columnList ?? []);
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
368
        }
369
370
        return $listValue;
371
    }
372
373
    /**
374
     * @param array|null $list
375
     *
376
     * @return array|null
377
     */
378
    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...
379
        foreach ($list as $listKey => $listValue) {
380
381
            $mergeColumArray = $this->mergeColumns($listValue);
382
            if(!empty($mergeColumArray)) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space(s) after IF keyword; 0 found
Loading history...
383
                $list[$listKey] = $mergeColumArray;
384
            }
385
386
            $formatArray = $this->getExportFormatFieldText ($listValue);
0 ignored issues
show
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...
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
387
            if ($formatArray) {
388
                $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

388
                $columNameList = array_keys(/** @scrutinizer ignore-type */ $formatArray);
Loading history...
389
                foreach ($formatArray as $formatKey => $formatValue) {
0 ignored issues
show
Bug introduced by
The expression $formatArray of type void is not traversable.
Loading history...
390
                    if (isset($formatValue['text'])) {
391
                        $list[$listKey][$formatKey] = $formatValue['text'];
392
                    }
393
394
                    if (isset($formatValue['rename'])) {
395
396
                        $list[$listKey][$formatValue['rename']] = $formatValue['text'] ?? ($list[$listKey][$formatKey] ?? null);
397
                        if ($formatValue['rename'] !== $formatKey) {
398
                            foreach ($columNameList as $columnKey => $columnValue) {
399
400
                                if ($formatKey === $columnValue) {
401
                                    $columNameList[$columnKey] = $formatValue['rename'];
402
                                }
403
                            }
404
405
                            unset($list[$listKey][$formatKey]);
406
                        }
407
                    }
408
                }
409
410
                if (isset($formatArray['reorderColumns']) && $formatArray['reorderColumns']) {
411
                    $list[$listKey] = $this->arrayCustomOrder($list[$listKey], $columNameList);
412
                }
413
            }
414
        }
415
416
        return $list;
417
    }
418
419
    /**
420
     * @param array $arrayToOrder
421
     * @param array $orderList
422
     *
423
     * @return array
424
     */
425
    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...
426
        $ordered = array();
427
        foreach ($orderList as $key) {
428
            if (array_key_exists($key, $arrayToOrder)) {
429
                $ordered[$key] = $arrayToOrder[$key];
430
            }
431
        }
432
        return $ordered;
433
    }
434
435
    /**
436
     * Count a record list
437
     * @TODO add total count / deleted count / active count
438
     *
439
     * @return \Phalcon\Http\ResponseInterface
440
     */
441
    public function countAction()
442
    {
443
        $model = $this->getModelClassName();
444
        
445
        /** @var \Zemit\Mvc\Model $entity */
446
        $entity = new $model();
447
        
448
        $this->view->totalCount = $model::count($this->getFindCount($this->getFind()));
449
        $this->view->totalCount = is_int($this->view->totalCount)? $this->view->totalCount : count($this->view->totalCount);
450
        $this->view->model = get_class($entity);
451
        $this->view->source = $entity->getSource();
452
        
453
        return $this->setRestResponse();
454
    }
455
    
456
    /**
457
     * Prepare a new model for the frontend
458
     *
459
     * @return \Phalcon\Http\ResponseInterface
460
     */
461
    public function newAction()
462
    {
463
        $model = $this->getModelClassName();
464
        
465
        /** @var \Zemit\Mvc\Model $entity */
466
        $entity = new $model();
467
        $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...
468
        
469
        $this->view->model = get_class($entity);
470
        $this->view->source = $entity->getSource();
471
        $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...
472
        
473
        return $this->setRestResponse();
474
    }
475
    
476
    /**
477
     * Prepare a new model for the frontend
478
     *
479
     * @return \Phalcon\Http\ResponseInterface
480
     */
481
    public function validateAction($id = null)
482
    {
483
        $model = $this->getModelClassName();
484
        
485
        /** @var \Zemit\Mvc\Model $entity */
486
        $entity = $this->getSingle($id);
487
        $new = !$entity;
0 ignored issues
show
introduced by
$entity is of type Zemit\Mvc\Model, thus it always evaluated to true.
Loading history...
488
        
489
        if ($new) {
0 ignored issues
show
introduced by
The condition $new is always false.
Loading history...
490
            $entity = new $model();
491
        }
492
        
493
        $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...
494
        
495
        /**
496
         * Event to run
497
         * @see https://docs.phalcon.io/4.0/en/db-models-events
498
         */
499
        $events = [
500
            'beforeCreate' => null,
501
            'beforeUpdate' => null,
502
            'beforeSave' => null,
503
            'beforeValidationOnCreate' => null,
504
            'beforeValidationOnUpdate' => null,
505
            'beforeValidation' => null,
506
            'prepareSave' => null,
507
            'validation' => null,
508
            'afterValidationOnCreate' => null,
509
            'afterValidationOnUpdate' => null,
510
            'afterValidation' => null,
511
        ];
512
        
513
        // run events, as it would normally
514
        foreach ($events as $event => $state) {
515
            $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...
516
            
517
            // skip depending wether it's a create or update
518
            if (strpos($event, $new ? 'Update' : 'Create') !== false) {
519
                continue;
520
            }
521
            
522
            // fire the event, allowing to fail or skip
523
            $events[$event] = $entity->fireEventCancel($event);
524
            if ($events[$event] === false) {
525
                // event failed
526
                break;
527
            }
528
            
529
            // event was skipped, just for consistencies purpose
530
            if ($this->skipped) {
531
                continue;
532
            }
533
        }
534
        
535
        $this->view->model = get_class($entity);
536
        $this->view->source = $entity->getSource();
537
        $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...
538
        $this->view->messages = $entity->getMessages();
539
        $this->view->events = $events;
540
        $this->view->validated = empty($this->view->messages);
541
        
542
        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

542
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->validated);
Loading history...
543
    }
544
    
545
    /**
546
     * Saving a record
547
     * - Create
548
     * - Update
549
     *
550
     * @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...
551
     *
552
     * @return array
553
     */
554
    public function saveAction($id = null)
555
    {
556
        $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

556
        $this->view->/** @scrutinizer ignore-call */ 
557
                     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...
557
        
558
        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...
559
    }
560
    
561
    /**
562
     * Deleting a record
563
     *
564
     * @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...
565
     *
566
     * @return bool
567
     */
568
    public function deleteAction($id = null)
569
    {
570
        $single = $this->getSingle($id);
571
        
572
        $this->view->deleted = $single ? $single->delete() : false;
573
        $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...
574
        $this->view->messages = $single ? $single->getMessages() : false;
575
        
576
        if (!$single) {
577
            $this->response->setStatusCode(404, 'Not Found');
578
            
579
            return false;
580
        }
581
        
582
        return $this->setRestResponse($this->view->deleted);
0 ignored issues
show
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...
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

582
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->deleted);
Loading history...
583
    }
584
    
585
    /**
586
     * Restoring record
587
     *
588
     * @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...
589
     *
590
     * @return bool
591
     */
592
    public function restoreAction($id = null)
593
    {
594
        $single = $this->getSingle($id);
595
        
596
        $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

596
        $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...
597
        $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...
598
        $this->view->messages = $single ? $single->getMessages() : false;
599
        
600
        if (!$single) {
601
            $this->response->setStatusCode(404, 'Not Found');
602
            
603
            return false;
604
        }
605
        
606
        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

606
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->restored);
Loading history...
607
    }
608
    
609
    /**
610
     * Re-ordering a position
611
     *
612
     * @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...
613
     * @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...
614
     *
615
     * @return bool|\Phalcon\Http\ResponseInterface
616
     */
617
    public function reorderAction($id = null)
618
    {
619
        $single = $this->getSingle($id);
620
        
621
        $position = $this->getParam('position', 'int');
622
        
623
        $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

623
        $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...
624
        $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...
625
        $this->view->messages = $single ? $single->getMessages() : false;
626
        
627
        if (!$single) {
628
            $this->response->setStatusCode(404, 'Not Found');
629
            
630
            return false;
631
        }
632
        
633
        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

633
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->reordered);
Loading history...
634
    }
635
    
636
    /**
637
     * Sending an error as an http response
638
     *
639
     * @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...
640
     * @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...
641
     *
642
     * @return \Phalcon\Http\ResponseInterface
643
     */
644
    public function setRestErrorResponse($code = 400, $status = 'Bad Request', $response = null)
645
    {
646
        return $this->setRestResponse($response, $code, $status);
647
    }
648
    
649
    /**
650
     * Sending rest response as an http response
651
     *
652
     * @param array|null $response
653
     * @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...
654
     * @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...
655
     * @param int $jsonOptions
656
     * @param int $depth
657
     *
658
     * @return \Phalcon\Http\ResponseInterface
659
     */
660
    public function setRestResponse($response = null, $code = null, $status = null, $jsonOptions = 0, $depth = 512)
661
    {
662
        $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...
663
        
664
        // keep forced status code or set our own
665
        $responseStatusCode = $this->response->getStatusCode();
666
        $reasonPhrase = $this->response->getReasonPhrase();
667
        $status ??= $reasonPhrase ? : 'OK';
668
        $code ??= (int)$responseStatusCode ? : 200;
669
        $view = $this->view->getParamsToView();
670
        $hash = hash('sha512', json_encode($view));
671
        
672
        /**
673
         * Debug section
674
         * - Versions
675
         * - Request
676
         * - Identity
677
         * - Profiler
678
         * - Dispatcher
679
         * - Router
680
         */
681
        $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

681
        $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

681
        $request = $debug ? $this->request->/** @scrutinizer ignore-call */ toArray() : null;
Loading history...
682
        $identity = $debug ? $this->identity->getIdentity() : null;
683
        $profiler = $debug && $this->profiler ? $this->profiler->toArray() : null;
684
        $dispatcher = $debug ? $this->dispatcher->toArray() : null;
685
        $router = $debug ? $this->router->toArray() : null;
686
        
687
        $api = $debug ? [
688
            'php' => phpversion(),
689
            'phalcon' => Version::get(),
690
            '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...
691
            'core' => $this->config->core->name,
692
            'app' => $this->config->app->version,
693
            'name' => $this->config->app->name,
694
        ] : [];
695
        $api['version'] = '0.1';
696
        
697
        $this->response->setStatusCode($code, $code . ' ' . $status);
698
        
699
        // @todo handle this correctly
700
        // @todo private vs public cache type
701
        $cache = $this->getCache();
702
        if (!empty($cache['lifetime'])) {
703
            if ($this->response->getStatusCode() === 200) {
704
                $this->response->setCache($cache['lifetime']);
705
                $this->response->setEtag($hash);
706
            }
707
        } else {
708
            $this->response->setCache(0);
709
            $this->response->setHeader('Cache-Control', 'no-cache, max-age=0');
710
        }
711
        
712
        return $this->response->setJsonContent(array_merge([
713
            'api' => $api,
714
            'timestamp' => date('c'),
715
            'hash' => $hash,
716
            'status' => $status,
717
            'code' => $code,
718
            'response' => $response,
719
            'view' => $view,
720
        ], $debug ? [
721
            'identity' => $identity,
722
            'profiler' => $profiler,
723
            'request' => $request,
724
            'dispatcher' => $dispatcher,
725
            'router' => $router,
726
        ] : []), $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

726
        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...
727
    }
728
    
729
    public function beforeExecuteRoute(Dispatcher $dispatcher) {
0 ignored issues
show
Unused Code introduced by
The parameter $dispatcher is not used and could be removed. ( Ignorable by Annotation )

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

729
    public function beforeExecuteRoute(/** @scrutinizer ignore-unused */ Dispatcher $dispatcher) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
730
        // @todo use eventsManager from service provider instead
731
        $this->eventsManager->enablePriorities(true);
732
        // @todo see if we can implement receiving an array of responses globally: V2
733
        // $this->eventsManager->collectResponses(true);
734
        
735
        // retrieve events based on the role & config
736
        $permissions = $this->config->path('permissions.roles')->toArray();
737
        foreach ($permissions as $role => $permission) {
738
            $behaviorsContext = $permission['behaviors'] ?? [];
739
            foreach ($behaviorsContext as $className => $behaviors) {
740
                if (is_int($className) || get_class($this) === $className) {
741
                    $this->attachBehaviors($behaviors);
742
                }
743
                if ($this->getModelClassName() === $className) {
744
                    $this->attachBehaviors($behaviors, 'model');
745
                }
746
            }
747
        }
748
    }
749
    
750
    public function attachBehaviors($behaviors, $eventType = 'rest') {
751
        if (!is_array($behaviors)) {
752
            $behaviors = [$behaviors];
753
        }
754
        foreach ($behaviors as $behavior) {
755
            $event = new $behavior();
756
            if ($event instanceof Injectable) {
757
                $event->setDI($this->getDI());
758
            }
759
            $this->eventsManager->attach($event->eventType ?? $eventType, $event, $event->priority ?? Manager::DEFAULT_PRIORITY);
0 ignored issues
show
Bug Best Practice introduced by
The property priority does not exist on Zemit\Di\Injectable. Since you implemented __get, consider adding a @property annotation.
Loading history...
Unused Code introduced by
The call to Phalcon\Events\ManagerInterface::attach() has too many arguments starting with $event->priority ?? Phal...nager::DEFAULT_PRIORITY. ( Ignorable by Annotation )

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

759
            $this->eventsManager->/** @scrutinizer ignore-call */ 
760
                                  attach($event->eventType ?? $eventType, $event, $event->priority ?? Manager::DEFAULT_PRIORITY);

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...
Bug Best Practice introduced by
The property eventType does not exist on Zemit\Di\Injectable. Since you implemented __get, consider adding a @property annotation.
Loading history...
760
        }
761
    }
762
    
763
    /**
764
     * Handle rest response automagically
765
     *
766
     * @param Dispatcher $dispatcher
767
     */
768
    public function afterExecuteRoute(Dispatcher $dispatcher)
769
    {
770
        $response = $dispatcher->getReturnedValue();
771
        
772
        // Avoid breaking default phalcon behaviour
773
        if ($response instanceof Response) {
774
            return;
775
        }
776
        
777
        // Merge response into view variables
778
        if (is_array($response)) {
779
            $this->view->setVars($response, true);
780
        }
781
        
782
        // Return our Rest normalized response
783
        $dispatcher->setReturnedValue($this->setRestResponse(is_array($response) ? null : $response));
784
    }
785
}
786