Test Failed
Push — master ( bb4b64...1f0c51 )
by Julien
04:52
created

Model   F

Complexity

Total Complexity 175

Size/Duplication

Total Lines 993
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 14
Bugs 7 Features 0
Metric Value
wmc 175
eloc 338
c 14
b 7
f 0
dl 0
loc 993
ccs 0
cts 359
cp 0
rs 2

47 Methods

Rating   Name   Duplication   Size   Complexity  
A getModelName() 0 3 1
A getWhiteList() 0 3 1
A getModelClassName() 0 3 1
A getColumnMap() 0 3 1
A getFlatWhiteList() 0 5 1
A getSearchWhiteList() 0 3 1
A getWith() 0 3 1
A getFilterWhiteList() 0 3 1
A getListExpose() 0 3 1
A setBind() 0 3 2
B getModelNameFromController() 0 16 7
A getGroup() 0 11 3
A getListWith() 0 3 1
A getSoftDeleteCondition() 0 3 1
A getParams() 0 16 3
A getCache() 0 12 2
A setBindTypes() 0 3 2
B appendModelName() 0 25 8
A getRoleList() 0 2 1
A getBindTypes() 0 3 1
A getColumns() 0 3 1
A getOffset() 0 3 1
B getIdentityCondition() 0 32 7
A getJoins() 0 3 1
A getBind() 0 3 1
A getFind() 0 24 4
B getSearchCondition() 0 31 7
A getCacheKey() 0 5 1
A getHaving() 0 3 1
A getOrder() 0 3 1
D getFilterCondition() 0 128 42
A getExpose() 0 3 1
A getLimit() 0 3 1
A getParam() 0 5 1
A getDistinct() 0 3 1
A getPermissionCondition() 0 3 1
D getContentType() 0 42 19
A getExportFormatFieldText() 0 2 1
A getExportExpose() 0 3 1
A getSingle() 0 17 4
A arrayMapRecursive() 0 7 2
C getRestMessages() 0 45 13
A getParamExplodeArrayMapFilter() 0 8 2
F save() 0 62 16
A getFindCount() 0 14 3
A getExportMergeColum() 0 3 1
A getConditions() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Model, and based on these observations, apply Extract Interface, too.

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 Phalcon\Db\Column;
14
use Phalcon\Messages\Message;
15
use Phalcon\Messages\Messages;
16
use Phalcon\Mvc\Model\Resultset;
17
use Phalcon\Mvc\ModelInterface;
18
use Phalcon\Text;
19
use Zemit\Http\Request;
20
use Zemit\Identity;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Zemit\Mvc\Controller\Identity. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
21
use Zemit\Mvc\Model\Expose\Expose;
22
use Zemit\Utils\Slug;
23
24
/**
25
 * Trait Model
26
 *
27
 * @author Julien Turbide <[email protected]>
28
 * @copyright Zemit Team <[email protected]>
29
 *
30
 * @since 1.0
31
 * @version 1.0
32
 *
33
 * @package Zemit\Mvc\Controller
34
 */
35
trait Model
36
{
37
    protected $_bind = [];
38
    protected $_bindTypes = [];
39
    
40
    /**
41
     * Get the current Model Name
42
     * @return string|null
43
     * @todo remove for v1
44
     *
45
     * @deprecated change to getModelClassName() instead
46
     */
47
    public function getModelName()
48
    {
49
        return $this->getModelClassName();
50
    }
51
    
52
    /**
53
     * Get the current Model Class Name
54
     *
55
     * @return string|null
56
     */
57
    public function getModelClassName()
58
    {
59
        return $this->getModelNameFromController();
60
    }
61
    
62
    /**
63
     * Get the WhiteList parameters for saving
64
     * @return null|array
65
     * @todo add a whitelist object that would be able to support one configuration for the search, assign, filter
66
     *
67
     */
68
    protected function getWhiteList()
69
    {
70
        return null;
71
    }
72
    
73
    /**
74
     * Get the Flattened WhiteList
75
     *
76
     * @param array|null $whiteList
77
     *
78
     * @return array|null
79
     */
80
    public function getFlatWhiteList(?array $whiteList = null)
81
    {
82
        $whiteList ??= $this->getWhiteList();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWhiteList() targeting Zemit\Mvc\Controller\Model::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...
83
        
84
        return array_keys(Expose::_parseColumnsRecursive($whiteList));
0 ignored issues
show
Bug introduced by
It seems like Zemit\Mvc\Model\Expose\E...nsRecursive($whiteList) can also be of type null; however, parameter $array of array_keys() 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

84
        return array_keys(/** @scrutinizer ignore-type */ Expose::_parseColumnsRecursive($whiteList));
Loading history...
85
    }
86
    
87
    /**
88
     * Get the WhiteList parameters for filtering
89
     *
90
     * @return null|array
91
     */
92
    protected function getFilterWhiteList()
93
    {
94
        return $this->getWhiteList();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWhiteList() targeting Zemit\Mvc\Controller\Model::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...
95
    }
96
    
97
    /**
98
     * Get the WhiteList parameters for filtering
99
     *
100
     * @return null|array
101
     */
102
    protected function getSearchWhiteList()
103
    {
104
        return $this->getFilterWhiteList();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getFilterWhiteList() targeting Zemit\Mvc\Controller\Model::getFilterWhiteList() 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
    }
106
    
107
    /**
108
     * Get the column mapping for crud
109
     *
110
     * @return null|array
111
     */
112
    protected function getColumnMap()
113
    {
114
        return null;
115
    }
116
    
117
    /**
118
     * Get relationship eager loading definition
119
     *
120
     * @return null|array
121
     */
122
    protected function getWith()
123
    {
124
        return null;
125
    }
126
    
127
    /**
128
     * Get relationship eager loading definition for a listing
129
     *
130
     * @return null|array
131
     */
132
    protected function getListWith()
133
    {
134
        return $this->getWith();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWith() targeting Zemit\Mvc\Controller\Model::getWith() 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...
135
    }
136
    
137
    /**
138
     * Get expose definition for a single entity
139
     *
140
     * @return null|array
141
     */
142
    protected function getExpose()
143
    {
144
        return null;
145
    }
146
    
147
    /**
148
     * Get expose definition for listing many entities
149
     *
150
     * @return null|array
151
     */
152
    protected function getListExpose()
153
    {
154
        return $this->getExpose();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Model::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...
155
    }
156
    
157
    /**
158
     * Get expose definition for export
159
     *
160
     * @return null|array
161
     */
162
    protected function getExportExpose()
163
    {
164
        return $this->getExpose();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Model::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...
165
    }
166
167
    /**
168
     * Get columns merge definition for export
169
     *
170
     * @return null|array
171
     */
172
    public function getExportMergeColum ()
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...
173
    {
174
        return null;
175
    }
176
177
    /**
178
     * Get columns format field text definition for export
179
     *
180
     * @param array|null $params
181
     *
182
     * @return null|array
183
     */
184
    public function getExportFormatFieldText (?array $params = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $params 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

184
    public function getExportFormatFieldText (/** @scrutinizer ignore-unused */ ?array $params = null) {

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...
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...
185
        return null;
186
    }
187
    
188
    /**
189
     * Get join definition
190
     *
191
     * @return null|array
192
     */
193
    protected function getJoins()
194
    {
195
        return null;
196
    }
197
    
198
    /**
199
     * Get the order definition
200
     *
201
     * @return null|array
202
     */
203
    protected function getOrder()
204
    {
205
        return $this->getParamExplodeArrayMapFilter('order');
206
    }
207
    
208
    /**
209
     * Get the current limit value
210
     *
211
     * @return null|int Default: 1000
212
     */
213
    protected function getLimit(): int
214
    {
215
        return (int)$this->getParam('limit', 'int', 1000);
216
    }
217
    
218
    /**
219
     * Get the current offset value
220
     *
221
     * @return null|int Default: 0
222
     */
223
    protected function getOffset(): int
224
    {
225
        return (int)$this->getParam('offset', 'int', 0);
226
    }
227
    
228
    /**
229
     * Get group
230
     * - Automatically group by ID by default if nothing else is provided
231
     * - This will fix multiple single records being returned for the same model with joins
232
     *
233
     * @return array[string]|string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment array[string]|string|null at position 1 could not be parsed: Expected ']' at position 1, but found '['.
Loading history...
234
     */
235
    protected function getGroup()
236
    {
237
        $group = $this->getParamExplodeArrayMapFilter('group');
238
        
239
        // Fix for joins, automatically append grouping if none provided
240
        $join = $this->getJoins();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $join is correct as $this->getJoins() targeting Zemit\Mvc\Controller\Model::getJoins() 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...
241
        if (empty($group) && !empty($join)) {
242
            $group = $this->appendModelName('id');
243
        }
244
        
245
        return $group;
246
    }
247
    
248
    /**
249
     * Get distinct
250
     * @TODO see how to implement this, maybe an action itself
251
     *
252
     * @return array[string]|string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment array[string]|string|null at position 1 could not be parsed: Expected ']' at position 1, but found '['.
Loading history...
253
     */
254
    protected function getDistinct()
255
    {
256
        return $this->getParamExplodeArrayMapFilter('distinct');
257
    }
258
    
259
    /**
260
     * Get columns
261
     * @TODO see how to implement this
262
     *
263
     * @return array[string]|string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment array[string]|string|null at position 1 could not be parsed: Expected ']' at position 1, but found '['.
Loading history...
264
     */
265
    protected function getColumns()
266
    {
267
        return $this->getParam('columns', 'string');
268
    }
269
    
270
    /**
271
     * Return the whitelisted role list for the current model
272
     *
273
     * @return string[] By default will return dev and admin role
274
     */
275
    protected function getRoleList() {
276
        return ['dev', 'admin'];
277
    }
278
    
279
    /**
280
     * Get Search condition
281
     *
282
     * @return string Default: deleted = 0
283
     */
284
    protected function getSearchCondition()
285
    {
286
        $conditions = [];
287
        
288
        $searchList = array_values(array_filter(array_unique(explode(' ', $this->getParam('search', 'string')))));
0 ignored issues
show
Bug introduced by
It seems like $this->getParam('search', 'string') can also be of type null and string[]; however, parameter $string of explode() does only seem to accept string, 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

288
        $searchList = array_values(array_filter(array_unique(explode(' ', /** @scrutinizer ignore-type */ $this->getParam('search', 'string')))));
Loading history...
289
        
290
        foreach ($searchList as $searchTerm) {
291
            $orConditions = [];
292
            $searchWhiteList = $this->getSearchWhiteList();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $searchWhiteList is correct as $this->getSearchWhiteList() targeting Zemit\Mvc\Controller\Model::getSearchWhiteList() 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...
293
            if ($searchWhiteList) {
294
                foreach ($searchWhiteList as $whiteList) {
0 ignored issues
show
Bug introduced by
The expression $searchWhiteList of type void is not traversable.
Loading history...
295
                    
296
                    // Multidimensional arrays not supported yet
297
                    // @todo support this properly
298
                    if (is_array($whiteList)) {
299
                        continue;
300
                    }
301
                    
302
                    $searchTermBinding = '_' . uniqid() . '_';
303
                    $orConditions [] = $this->appendModelName($whiteList) . " like :$searchTermBinding:";
0 ignored issues
show
Bug introduced by
Are you sure $this->appendModelName($whiteList) of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

303
                    $orConditions [] = /** @scrutinizer ignore-type */ $this->appendModelName($whiteList) . " like :$searchTermBinding:";
Loading history...
304
                    $this->setBind([$searchTermBinding => '%' . $searchTerm . '%']);
305
                    $this->setBindTypes([$searchTermBinding => Column::BIND_PARAM_STR]);
306
                }
307
            }
308
            
309
            if (!empty($orConditions)) {
310
                $conditions [] = '(' . implode(' or ', $orConditions) . ')';
311
            }
312
        }
313
        
314
        return empty($conditions) ? null : '(' . implode(' and ', $conditions) . ')';
315
    }
316
    
317
    /**
318
     * Get Soft delete condition
319
     *
320
     * @return string Default: deleted = 0
321
     */
322
    protected function getSoftDeleteCondition()
323
    {
324
        return '[' . $this->getModelName() . '].[deleted] = 0';
0 ignored issues
show
Deprecated Code introduced by
The function Zemit\Mvc\Controller\Model::getModelName() has been deprecated: change to getModelClassName() instead ( Ignorable by Annotation )

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

324
        return '[' . /** @scrutinizer ignore-deprecated */ $this->getModelName() . '].[deleted] = 0';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
325
    }
326
    
327
    /**
328
     * @param $field
329
     * @param string $sanitizer
330
     * @param string $glue
331
     *
332
     * @return array|string[]
333
     */
334
    public function getParamExplodeArrayMapFilter($field, $sanitizer = 'string', $glue = ',')
335
    {
336
        $filter = $this->filter;
337
        $ret = array_filter(array_map(function($e) use ($filter, $sanitizer) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
338
            return $this->appendModelName(trim($filter->sanitize($e, $sanitizer)));
339
        }, explode($glue, $this->getParam($field, $sanitizer))));
0 ignored issues
show
Bug introduced by
It seems like $this->getParam($field, $sanitizer) can also be of type null and string[]; however, parameter $string of explode() does only seem to accept string, 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

339
        }, explode($glue, /** @scrutinizer ignore-type */ $this->getParam($field, $sanitizer))));
Loading history...
340
        
341
        return empty($ret) ? null : $ret;
342
    }
343
    
344
    /**
345
     * Set the variables to bind
346
     *
347
     * @param array $bind Variable bind to merge or replace
348
     * @param bool $replace Pass true to replace the entire bind set
349
     */
350
    public function setBind(array $bind = [], bool $replace = false)
351
    {
352
        $this->_bind = $replace ? $bind : array_merge($this->getBind(), $bind);
0 ignored issues
show
Bug introduced by
It seems like $this->getBind() can also be of type null; however, parameter $arrays of array_merge() 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

352
        $this->_bind = $replace ? $bind : array_merge(/** @scrutinizer ignore-type */ $this->getBind(), $bind);
Loading history...
353
    }
354
    
355
    /**
356
     * Get the current bind
357
     * key => value
358
     *
359
     * @return array|null
360
     */
361
    public function getBind()
362
    {
363
        return $this->_bind ?? null;
364
    }
365
    
366
    /**
367
     * Set the variables types to bind
368
     *
369
     * @param array $bindTypes Variable bind types to merge or replace
370
     * @param bool $replace Pass true to replace the entire bind type set
371
     */
372
    public function setBindTypes(array $bindTypes = [], bool $replace = false)
373
    {
374
        $this->_bindTypes = $replace ? $bindTypes : array_merge($this->getBindTypes(), $bindTypes);
0 ignored issues
show
Bug introduced by
It seems like $this->getBindTypes() can also be of type null; however, parameter $arrays of array_merge() 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

374
        $this->_bindTypes = $replace ? $bindTypes : array_merge(/** @scrutinizer ignore-type */ $this->getBindTypes(), $bindTypes);
Loading history...
375
    }
376
    
377
    /**
378
     * Get the current bind types
379
     *
380
     * @return array|null
381
     */
382
    public function getBindTypes()
383
    {
384
        return $this->_bindTypes ?? null;
385
    }
386
    
387
    /**
388
     * Get Created By Condition
389
     *
390
     * @param string[] $columns
391
     * @param Identity|null $identity
392
     * @param string[]|null $roleList
393
     *
394
     * @return null
395
     *
396
     * @return string|null
397
     */
398
    protected function getIdentityCondition(array $columns = null, Identity $identity = null, $roleList = null)
399
    {
400
        $identity ??= $this->identity ?? false;
401
        $roleList ??= $this->getRoleList();
402
        $modelName = $this->getModelClassName();
403
        
404
        if ($modelName && $identity && !$identity->hasRole($roleList)) {
405
            $ret = [];
406
            
407
            $columns ??= [
408
                'createdBy',
409
                'ownedBy',
410
                'userId',
411
            ];
412
            
413
            foreach ($columns as $column) {
414
                if (!property_exists($modelName, $column)) {
415
                    continue;
416
                }
417
                
418
                $field = strpos($column, '.') !== false ? $column : $modelName . '.' . $column;
419
                $field = '[' . str_replace('.', '].[', $field) . ']';
420
                
421
                $this->setBind([$column => (int)$identity->getUserId()]);
422
                $this->setBindTypes([$column => Column::BIND_PARAM_INT]);
423
                $ret [] = $field . ' = :' . $column . ':';
424
            }
425
            
426
            return implode(' or ', $ret);
0 ignored issues
show
Bug Best Practice introduced by
The expression return implode(' or ', $ret) returns the type string which is incompatible with the documented return type null.
Loading history...
427
        }
428
        
429
        return null;
430
    }
431
    
432
    function arrayMapRecursive($callback, $array)
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 arrayMapRecursive.

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...
433
    {
434
        $func = function($item) use (&$func, &$callback) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
435
            return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
436
        };
437
        
438
        return array_map($func, $array);
439
    }
440
    
441
    /**
442
     * Get Filter Condition
443
     *
444
     * @param array|null $filters
445
     * @param array|null $whiteList
446
     * @param bool $or
447
     *
448
     * @return string|null Return the generated query
449
     * @throws \Exception Throw an exception if the field property is not valid
450
     * @todo escape fields properly
451
     *
452
     */
453
    protected function getFilterCondition(array $filters = null, array $whiteList = null, $or = false)
454
    {
455
        $filters ??= $this->getParam('filters');
456
        $whiteList ??= $this->getFilterWhiteList();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getFilterWhiteList() targeting Zemit\Mvc\Controller\Model::getFilterWhiteList() 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...
457
        $whiteList = $this->getFlatWhiteList($whiteList);
458
        $lowercaseWhiteList = !is_null($whiteList) ? $this->arrayMapRecursive('mb_strtolower', $whiteList) : $whiteList;
0 ignored issues
show
introduced by
The condition is_null($whiteList) is always false.
Loading history...
459
        
460
        // No filter, no query
461
        if (empty($filters)) {
462
            return null;
463
        }
464
        
465
        $query = [];
466
        foreach ($filters as $filter) {
467
            $field = $this->filter->sanitize($filter['field'] ?? null, ['string', 'trim']);
468
            
469
            if (!empty($field)) {
470
                $lowercaseField = mb_strtolower($field);
471
                
472
                // whiteList on filter condition
473
                if (is_null($whiteList) || !in_array($lowercaseField, $lowercaseWhiteList, true)) {
474
                    // @todo if config is set to throw exception on usage of not allowed filters otherwise continue looping through
475
                    throw new \Exception('Not allowed to filter using the following field: `' . $field . '`', 403);
476
                    continue;
0 ignored issues
show
Unused Code introduced by
ContinueNode is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
477
                }
478
                
479
                $uniqid = substr(md5(json_encode($filter)), 0, 10);
480
//                $queryField = '_' . uniqid($uniqid . '_field_') . '_';
481
                $queryValue = '_' . uniqid($uniqid . '_value_') . '_';
482
                $queryOperator = strtolower($filter['operator']);
483
                switch($queryOperator) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space(s) after SWITCH keyword; 0 found
Loading history...
484
                    case '=': // Equal operator
485
                    case '!=': // Not equal operator
486
                    case '<>': // Not equal operator
487
                    case '>': // Greater than operator
488
                    case '>=': // Greater than or equal operator
489
                    case '<': // Less than or equal operator
490
                    case '<=': // Less than or equal operator
491
                    case '<=>': // NULL-safe equal to operator
492
                    case 'in': // Whether a value is within a set of values
493
                    case 'not in': // Whether a value is not within a set of values
494
                    case 'like': // Simple pattern matching
495
                    case 'not like': // Negation of simple pattern matching
496
                    case 'between': // Whether a value is within a range of values
497
                    case 'not between': // Whether a value is not within a range of values
498
                    case 'is': // Test a value against a boolean
499
                    case 'is not': // Test a value against a boolean
500
                    case 'is null': // NULL value test
501
                    case 'is not null': // NOT NULL value test
502
                    case 'is false': // Test a value against a boolean
503
                    case 'is not false': // // Test a value against a boolean
504
                    case 'is true': // // Test a value against a boolean
505
                    case 'is not true': // // Test a value against a boolean
506
                        break;
507
                    default:
508
                        throw new \Exception('Not allowed to filter using the following operator: `' . $queryOperator . '`', 403);
509
                        break;
510
                }
511
                
512
                $bind = [];
513
                $bindType = [];
514
515
//                $bind[$queryField] = $filter['field'];
516
//                $bindType[$queryField] = Column::BIND_PARAM_STR;
517
//                $queryFieldBinder = ':' . $queryField . ':';
518
//                $queryFieldBinder = '{' . $queryField . '}';
519
                
520
                // Add the current model name by default
521
                $field = $this->appendModelName($field);
522
                
523
                $queryFieldBinder = $field;
524
                $queryValueBinder = ':' . $queryValue . ':';
525
                if (isset($filter['value'])) {
526
                    // special for between and not between
527
                    if (in_array($queryOperator, ['between', 'not between'])) {
528
                        $queryValue0 = '_' . uniqid($uniqid . '_value_') . '_';
529
                        $queryValue1 = '_' . uniqid($uniqid . '_value_') . '_';
530
                        $bind[$queryValue0] = $filter['value'][0];
531
                        $bind[$queryValue1] = $filter['value'][1];
532
                        $bindType[$queryValue0] = Column::BIND_PARAM_STR;
533
                        $bindType[$queryValue1] = Column::BIND_PARAM_STR;
534
                        $query [] = (($queryOperator === 'not between')? 'not ' : null) . "$queryFieldBinder between :$queryValue0: and :$queryValue1:";
535
                    }
536
                    else {
537
                        $bind[$queryValue] = $filter['value'];
538
                        if (is_string($filter['value'])) {
539
                            $bindType[$queryValue] = Column::BIND_PARAM_STR;
540
                        }
541
                        else if (is_int($filter['value'])) {
542
                            $bindType[$queryValue] = Column::BIND_PARAM_INT;
543
                        }
544
                        else if (is_bool($filter['value'])) {
545
                            $bindType[$queryValue] = Column::BIND_PARAM_BOOL;
546
                        }
547
                        else if (is_float($filter['value'])) {
548
                            $bindType[$queryValue] = Column::BIND_PARAM_DECIMAL;
549
                        }
550
                        else if (is_double($filter['value'])) {
551
                            $bindType[$queryValue] = Column::BIND_PARAM_DECIMAL;
552
                        }
553
                        else if (is_array($filter['value'])) {
554
                            $queryValueBinder = '({' . $queryValue . ':array})';
555
                            $bindType[$queryValue] = Column::BIND_PARAM_STR;
556
                        }
557
                        else {
558
                            $bindType[$queryValue] = Column::BIND_PARAM_NULL;
559
                        }
560
                        $query [] = "$queryFieldBinder $queryOperator $queryValueBinder";
561
                    }
562
                }
563
                else {
564
                    $query [] = "$queryFieldBinder $queryOperator";
565
                }
566
                
567
                $this->setBind($bind);
568
                $this->setBindTypes($bindType);
569
            }
570
            else {
571
                if (is_array($filter) || $filter instanceof \Traversable) {
572
                    $query [] = $this->getFilterCondition($filter, $whiteList, !$or);
0 ignored issues
show
Bug introduced by
It seems like $filter can also be of type Traversable; however, parameter $filters of Zemit\Mvc\Controller\Model::getFilterCondition() 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

572
                    $query [] = $this->getFilterCondition(/** @scrutinizer ignore-type */ $filter, $whiteList, !$or);
Loading history...
573
                }
574
                else {
575
                    throw new \Exception('A valid field property is required.', 400);
576
                }
577
            }
578
        }
579
        
580
        return empty($query) ? null : '(' . implode($or ? ' or ' : ' and ', $query) . ')';
581
    }
582
    
583
    /**
584
     * Append the current model name alias to the field
585
     * So: field -> [Alias].[field]
586
     *
587
     * @param string|array $field
588
     * @param null $modelName
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $modelName is correct as it would always require null to be passed?
Loading history...
589
     *
590
     * @return array|string
591
     */
592
    public function appendModelName($field, $modelName = null)
593
    {
594
        $modelName ??= $this->getModelClassName();
595
        
596
        if (empty($field)) {
597
            return $field;
598
        }
599
        
600
        if (is_string($field)) {
601
            // Add the current model name by default
602
            $explode = explode(' ', $field);
603
            if (!strpos($field, '.') !== false) {
604
                $field = trim('[' . $modelName . '].[' . array_shift($explode) . '] ' . implode(' ', $explode));
605
            }
606
            else if (strpos($field, ']') === false && strpos($field, '[') === false) {
607
                $field = trim('[' . implode('].[', explode('.', array_shift($explode))) . ']' . implode(' ', $explode));
608
            }
609
        }
610
        else if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
611
            foreach ($field as $fieldKey => $fieldValue) {
612
                $field[$fieldKey] = $this->appendModelName($fieldValue, $modelName);
0 ignored issues
show
Bug introduced by
It seems like $modelName can also be of type string; however, parameter $modelName of Zemit\Mvc\Controller\Model::appendModelName() does only seem to accept 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

612
                $field[$fieldKey] = $this->appendModelName($fieldValue, /** @scrutinizer ignore-type */ $modelName);
Loading history...
613
            }
614
        }
615
        
616
        return $field;
617
    }
618
    
619
    /**
620
     * Get Permission Condition
621
     *
622
     * @return null
623
     */
624
    protected function getPermissionCondition($type = null, $identity = null)
0 ignored issues
show
Unused Code introduced by
The parameter $identity 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

624
    protected function getPermissionCondition($type = null, /** @scrutinizer ignore-unused */ $identity = null)

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...
Unused Code introduced by
The parameter $type 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

624
    protected function getPermissionCondition(/** @scrutinizer ignore-unused */ $type = null, $identity = null)

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...
625
    {
626
        return null;
627
    }
628
    
629
    /**
630
     * Get all conditions
631
     *
632
     * @return string
633
     * @throws \Exception
634
     */
635
    protected function getConditions()
636
    {
637
        $conditions = array_values(array_unique(array_filter([
638
            $this->getSoftDeleteCondition(),
639
            $this->getIdentityCondition(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getIdentityCondition() targeting Zemit\Mvc\Controller\Model::getIdentityCondition() 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...
640
            $this->getFilterCondition(),
641
            $this->getSearchCondition(),
642
            $this->getPermissionCondition(),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getPermissionCondition() targeting Zemit\Mvc\Controller\Mod...etPermissionCondition() 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...
643
        ])));
644
        
645
        return '(' . implode(') and (', $conditions) . ')';
646
    }
647
    
648
    /**
649
     * Get having conditions
650
     */
651
    public function getHaving()
652
    {
653
        return null;
654
    }
655
    
656
    /**
657
     * Get a cache key from params
658
     *
659
     * @param array|null $params
660
     *
661
     * @return string|null
662
     */
663
    public function getCacheKey(?array $params = null): ?string
664
    {
665
        $params ??= $this->getParams();
666
        
667
        return Slug::generate(json_encode($params, JSON_UNESCAPED_SLASHES));
668
    }
669
    
670
    /**
671
     * Get cache setting
672
     *
673
     * @param array|null $params
674
     *
675
     * @return array|null
676
     */
677
    public function getCache(?array $params = null)
678
    {
679
        $params ??= $this->getParams();
680
        
681
        if (!empty($params['cache'])) {
682
            return [
683
                'lifetime' => (int)$params['cache'],
684
                'key' => $this->getCacheKey($params),
685
            ];
686
        }
687
        
688
        return null;
689
    }
690
    
691
    
692
    /**
693
     * Get requested content type
694
     * - Default will return csv
695
     *
696
     * @param array|null $params
697
     *
698
     * @return string
699
     * @throws \Exception
700
     */
701
    public function getContentType(?array $params = null)
702
    {
703
        $params ??= $this->getParams();
704
        
705
        $contentType = strtolower($params['contentType'] ?? $params['content-type'] ?? 'json');
706
        
707
        switch($contentType) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space(s) after SWITCH keyword; 0 found
Loading history...
708
            case 'html':
709
            case 'text/html':
710
            case 'application/html':
711
                // html not supported yet
712
                break;
713
            case 'xml':
714
            case 'text/xml':
715
            case 'application/xml':
716
                // xml not supported yet
717
                break;
718
            case 'text':
719
            case 'text/plain':
720
                // plain text not supported yet
721
                break;
722
            case 'json':
723
            case 'text/json':
724
            case 'application/json':
725
                return 'json';
726
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
727
            case 'csv':
728
            case 'text/csv':
729
                return 'csv';
730
                break;
731
            case 'xlsx':
732
            case 'application/xlsx':
733
            case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
734
                return 'xlsx';
735
                break;
736
            case 'xls':
737
            case 'application/vnd.ms-excel':
738
                // old xls not supported yet
739
                break;
740
        }
741
        
742
        throw new \Exception('`' . $contentType . '` is not supported.', 400);
743
    }
744
    
745
    /**
746
     * Get find definition
747
     *
748
     * @return array
749
     * @throws \Exception
750
     */
751
    protected function getFind()
752
    {
753
        $find = [];
754
        $find['conditions'] = $this->getConditions();
755
        $find['bind'] = $this->getBind();
756
        $find['bindTypes'] = $this->getBindTypes();
757
        $find['limit'] = $this->getLimit();
758
        $find['offset'] = $this->getOffset();
759
        $find['order'] = $this->getOrder();
760
        $find['columns'] = $this->getColumns();
761
        $find['distinct'] = $this->getDistinct();
762
        $find['joins'] = $this->getJoins();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $find['joins'] is correct as $this->getJoins() targeting Zemit\Mvc\Controller\Model::getJoins() 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...
763
        $find['group'] = $this->getGroup();
764
        $find['having'] = $this->getHaving();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $find['having'] is correct as $this->getHaving() targeting Zemit\Mvc\Controller\Model::getHaving() 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...
765
        $find['cache'] = $this->getCache();
766
        
767
        // fix for grouping by multiple fields, phalcon only allow string here
768
        foreach (['distinct', 'group'] as $findKey) {
769
            if (isset($find[$findKey]) && is_array($find[$findKey])) {
770
                $find[$findKey] = implode(', ', $find[$findKey]);
771
            }
772
        }
773
        
774
        return array_filter($find);
775
    }
776
    
777
    /**
778
     * Return find lazy loading config for count
779
     * @return array|string
780
     */
781
    protected function getFindCount($find = null)
782
    {
783
        $find ??= $this->getFind();
784
        if (isset($find['limit'])) {
785
            unset($find['limit']);
786
        }
787
        if (isset($find['offset'])) {
788
            unset($find['offset']);
789
        }
790
//        if (isset($find['group'])) {
791
//            unset($find['group']);
792
//        }
793
        
794
        return array_filter($find);
795
    }
796
    
797
    /**
798
     * @param string $key
799
     * @param string[]|string|null $filters
800
     * @param string|null $default
801
     * @param array|null $params
802
     *
803
     * @return string[]|string|null
804
     */
805
    public function getParam(string $key, $filters = null, string $default = null, array $params = null)
806
    {
807
        $params ??= $this->getParams();
808
        
809
        return $this->filter->sanitize($params[$key] ?? $this->dispatcher->getParam($key, $filters, $default), $filters);
810
    }
811
    
812
    /**
813
     * Get parameters from
814
     * - JsonRawBody, post, put or get
815
     * @return mixed
816
     */
817
    protected function getParams(array $filters = null)
818
    {
819
        /** @var Request $request */
820
        $request = $this->request;
821
        
822
        if (!empty($filters)) {
823
            foreach ($filters as $filter) {
824
                $request->setParameterFilters($filter['name'], $filter['filters'], $filter['scope']);
825
            }
826
        }
827
828
//        $params = empty($request->getRawBody()) ? [] : $request->getJsonRawBody(true); // @TODO handle this differently
829
        return array_merge_recursive(
830
            $request->getFilteredQuery(), // $_GET
831
            $request->getFilteredPut(), // $_PUT
832
            $request->getFilteredPost(), // $_POST
833
        );
834
    }
835
    
836
    /**
837
     * Get Single from ID and Model Name
838
     *
839
     * @param string|int|null $id
840
     * @param string|null $modelName
841
     * @param string|array|null $with
842
     *
843
     * @return bool|Resultset
844
     */
845
    public function getSingle($id = null, $modelName = null, $with = [], $find = null, $appendCondition = true)
846
    {
847
        $id ??= (int)$this->getParam('id', 'int');
848
        $modelName ??= $this->getModelClassName();
849
        $with ??= $this->getWith();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWith() targeting Zemit\Mvc\Controller\Model::getWith() 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...
850
        $find ??= $this->getFind();
851
        $condition = '[' . $modelName . '].[id] = ' . (int)$id;
852
        if ($appendCondition) {
853
            $find['conditions'] .= (empty($find['conditions']) ? null : ' and ') . $condition;
854
        }
855
        else {
856
            $find['bind'] = [];
857
            $find['bindTypes'] = [];
858
            $find['conditions'] = $condition;
859
        }
860
        
861
        return $id ? $modelName::findFirstWith($with ?? [], $find ?? []) : false;
0 ignored issues
show
Bug introduced by
The method findFirstWith() does not exist on null. ( Ignorable by Annotation )

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

861
        return $id ? $modelName::/** @scrutinizer ignore-call */ findFirstWith($with ?? [], $find ?? []) : 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...
862
    }
863
    
864
    /**
865
     * Saving model automagically
866
     *
867
     * Note:
868
     * If a newly created entity can't be retrieved using the ->getSingle
869
     * method after it's creation, the entity will be returned directly
870
     *
871
     * @TODO Support Composite Primary Key
872
     *
873
     * @param null|int|string $id
874
     * @param null|\Zemit\Mvc\Model $entity
875
     * @param null|mixed $post
876
     * @param null|string $modelName
877
     * @param null|array $whiteList
878
     * @param null|array $columnMap
879
     * @param null|array $with
880
     *
881
     * @return array
882
     */
883
    protected function save($id = null, $entity = null, $post = null, $modelName = null, $whiteList = null, $columnMap = null, $with = null)
884
    {
885
        $single = false;
886
        $retList = [];
887
        
888
        // Get the model name to play with
889
        $modelName ??= $this->getModelClassName();
890
        $post ??= $this->getParams();
891
        $whiteList ??= $this->getWhiteList();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWhiteList() targeting Zemit\Mvc\Controller\Model::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...
892
        $columnMap ??= $this->getColumnMap();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getColumnMap() targeting Zemit\Mvc\Controller\Model::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...
893
        $with ??= $this->getWith();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWith() targeting Zemit\Mvc\Controller\Model::getWith() 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...
894
        $id = (int)$id;
895
        
896
        // Check if multi-d post
897
        if (!empty($id) || !isset($post[0]) || !is_array($post[0])) {
898
            $single = true;
899
            $post = [$post];
900
        }
901
        
902
        // Save each posts
903
        foreach ($post as $key => $singlePost) {
904
            $ret = [];
905
            
906
            // @todo see if we should remove this earlier
907
            if (isset($singlePost['_url'])) {
908
                unset($singlePost['_url']);
909
            }
910
            
911
            $singlePostId = (!$single || empty($id)) ? $this->getParam('id', 'int', $this->getParam('int', 'int', null)) : $id;
0 ignored issues
show
Bug introduced by
It seems like $this->getParam('int', 'int', null) can also be of type string[]; however, parameter $default of Zemit\Mvc\Controller\Model::getParam() does only seem to accept null|string, 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

911
            $singlePostId = (!$single || empty($id)) ? $this->getParam('id', 'int', /** @scrutinizer ignore-type */ $this->getParam('int', 'int', null)) : $id;
Loading history...
912
            unset($singlePost['id']);
913
            
914
            /** @var \Zemit\Mvc\Model $singlePostEntity */
915
            $singlePostEntity = (!$single || !isset($entity)) ? $this->getSingle($singlePostId, $modelName) : $entity;
0 ignored issues
show
Bug introduced by
It seems like $singlePostId can also be of type string[]; however, parameter $id of Zemit\Mvc\Controller\Model::getSingle() does only seem to accept integer|null|string, 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

915
            $singlePostEntity = (!$single || !isset($entity)) ? $this->getSingle(/** @scrutinizer ignore-type */ $singlePostId, $modelName) : $entity;
Loading history...
916
            
917
            // Create entity if not exists
918
            if (!$singlePostEntity && empty($singlePostId)) {
919
                $singlePostEntity = new $modelName();
920
            }
921
            
922
            if (!$singlePostEntity) {
923
                $ret = [
924
                    'saved' => false,
925
                    'messages' => [new Message('Entity id `' . $singlePostId . '` not found.', $modelName, 'NotFound', 404)],
926
                    'model' => $modelName,
927
                    'source' => (new $modelName)->getSource(),
928
                ];
929
            }
930
            else {
931
                $singlePostEntity->assign($singlePost, $whiteList, $columnMap);
932
                $ret['saved'] = $singlePostEntity->save();
933
                $ret['messages'] = $singlePostEntity->getMessages();
934
                $ret['model'] = get_class($singlePostEntity);
935
                $ret['source'] = $singlePostEntity->getSource();
936
                $ret['entity'] = $singlePostEntity->expose($this->getExpose());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Model::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...
937
                $fetch = $this->getSingle($singlePostEntity->getId(), $modelName, $with);
0 ignored issues
show
Bug introduced by
The method getId() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

937
                $fetch = $this->getSingle($singlePostEntity->/** @scrutinizer ignore-call */ getId(), $modelName, $with);
Loading history...
938
                $ret[$single ? 'single' : 'list'] = $fetch ? $fetch->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

938
                $ret[$single ? 'single' : 'list'] = $fetch ? $fetch->/** @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\Model::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...
939
            }
940
            
941
            $retList [] = $ret;
942
        }
943
        
944
        return $single ? $retList[0] : $retList;
945
    }
946
    
947
    /**
948
     * Try to find the appropriate model from the current controller name
949
     *
950
     * @param string $controllerName
951
     * @param array $namespaces
952
     * @param string $needle
953
     *
954
     * @return string|null
955
     */
956
    public function getModelNameFromController(string $controllerName = null, array $namespaces = null, string $needle = 'Models'): ?string
957
    {
958
        $controllerName ??= $this->dispatcher->getControllerName() ?? '';
959
        $namespaces ??= $this->loader->getNamespaces() ?? [];
960
        
961
        $model = ucfirst(Text::camelize(Text::uncamelize($controllerName)));
962
        if (!class_exists($model)) {
963
            foreach ($namespaces as $namespace => $path) {
964
                $possibleModel = $namespace . '\\' . $model;
965
                if (strpos($namespace, $needle) !== false && class_exists($possibleModel)) {
966
                    $model = $possibleModel;
967
                }
968
            }
969
        }
970
        
971
        return class_exists($model) && new $model() instanceof ModelInterface ? $model : null;
972
    }
973
    
974
    /**
975
     * Get message from list of entities
976
     *
977
     * @param $list Resultset|\Phalcon\Mvc\Model
978
     *
979
     * @return array|bool
980
     * @deprecated
981
     *
982
     */
983
    public function getRestMessages($list = null)
984
    {
985
        if (!is_array($list)) {
986
            $list = [$list];
987
        }
988
        
989
        $ret = [];
990
        
991
        foreach ($list as $single) {
992
            
993
            if ($single) {
994
                
995
                /** @var Messages $validations */
996
                $messages = $single instanceof Message ? $list : $single->getMessages();
997
                
998
                if ($messages && (is_array($messages) || $messages instanceof \Traversable)) {
999
                    
1000
                    foreach ($messages as $message) {
1001
                        
1002
                        $validationFields = $message->getField();
1003
                        
1004
                        if (!is_array($validationFields)) {
1005
                            $validationFields = [$validationFields];
1006
                        }
1007
                        
1008
                        foreach ($validationFields as $validationField) {
1009
                            
1010
                            if (empty($ret[$validationField])) {
1011
                                $ret[$validationField] = [];
1012
                            }
1013
                            
1014
                            $ret[$validationField][] = [
1015
                                'field' => $message->getField(),
1016
                                'code' => $message->getCode(),
1017
                                'type' => $message->getType(),
1018
                                'message' => $message->getMessage(),
1019
                                'metaData' => $message->getMetaData(),
1020
                            ];
1021
                        }
1022
                    }
1023
                }
1024
            }
1025
        }
1026
        
1027
        return $ret ? : false;
1028
    }
1029
}
1030