Test Failed
Push — master ( 2f15b8...6e5f5c )
by Julien
04:42
created

Model::getExportExpose()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
ccs 0
cts 2
cp 0
rs 10
cc 1
nc 1
nop 0
crap 2
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 expose definition for a single entity
129
     *
130
     * @return null|array
131
     */
132
    protected function getExpose()
133
    {
134
        return null;
135
    }
136
    
137
    /**
138
     * Get expose definition for listing many entities
139
     *
140
     * @return null|array
141
     */
142
    protected function getListExpose()
143
    {
144
        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...
145
    }
146
    
147
    /**
148
     * Get expose definition for export
149
     *
150
     * @return null|array
151
     */
152
    protected function getExportExpose()
153
    {
154
        return $this->getExportExpose();
155
    }
156
    
157
    /**
158
     * Get join definition
159
     *
160
     * @return null|array
161
     */
162
    protected function getJoins()
163
    {
164
        return null;
165
    }
166
    
167
    /**
168
     * Get the order definition
169
     *
170
     * @return null|array
171
     */
172
    protected function getOrder()
173
    {
174
        return $this->getParamExplodeArrayMapFilter('order');
175
    }
176
    
177
    /**
178
     * Get the current limit value
179
     *
180
     * @return null|int Default: 1000
181
     */
182
    protected function getLimit(): int
183
    {
184
        return (int)$this->getParam('limit', 'int', 1000);
185
    }
186
    
187
    /**
188
     * Get the current offset value
189
     *
190
     * @return null|int Default: 0
191
     */
192
    protected function getOffset(): int
193
    {
194
        return (int)$this->getParam('offset', 'int', 0);
195
    }
196
    
197
    /**
198
     * Get group
199
     * - Automatically group by ID by default if nothing else is provided
200
     * - This will fix multiple single records being returned for the same model with joins
201
     *
202
     * @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...
203
     */
204
    protected function getGroup()
205
    {
206
        $group = $this->getParamExplodeArrayMapFilter('group');
207
        
208
        // Fix for joins, automatically append grouping if none provided
209
        $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...
210
        if (empty($group) && !empty($join)) {
211
            $group = $this->appendModelName('id');
212
        }
213
        
214
        return $group;
215
    }
216
    
217
    /**
218
     * Get distinct
219
     * @TODO see how to implement this, maybe an action itself
220
     *
221
     * @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...
222
     */
223
    protected function getDistinct()
224
    {
225
        return $this->getParamExplodeArrayMapFilter('distinct');
226
    }
227
    
228
    /**
229
     * Get columns
230
     * @TODO see how to implement this
231
     *
232
     * @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...
233
     */
234
    protected function getColumns()
235
    {
236
        return $this->getParam('columns', 'string');
237
    }
238
    
239
    /**
240
     * Return the whitelisted role list for the current model
241
     *
242
     * @return string[] By default will return dev and admin role
243
     */
244
    protected function getRoleList() {
245
        return ['dev', 'admin'];
246
    }
247
    
248
    /**
249
     * Get Search condition
250
     *
251
     * @return string Default: deleted = 0
252
     */
253
    protected function getSearchCondition()
254
    {
255
        $conditions = [];
256
        
257
        $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

257
        $searchList = array_values(array_filter(array_unique(explode(' ', /** @scrutinizer ignore-type */ $this->getParam('search', 'string')))));
Loading history...
258
        
259
        foreach ($searchList as $searchTerm) {
260
            $orConditions = [];
261
            $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...
262
            if ($searchWhiteList) {
263
                foreach ($searchWhiteList as $whiteList) {
0 ignored issues
show
Bug introduced by
The expression $searchWhiteList of type void is not traversable.
Loading history...
264
                    
265
                    // Multidimensional arrays not supported yet
266
                    // @todo support this properly
267
                    if (is_array($whiteList)) {
268
                        continue;
269
                    }
270
                    
271
                    $searchTermBinding = '_' . uniqid() . '_';
272
                    $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

272
                    $orConditions [] = /** @scrutinizer ignore-type */ $this->appendModelName($whiteList) . " like :$searchTermBinding:";
Loading history...
273
                    $this->setBind([$searchTermBinding => '%' . $searchTerm . '%']);
274
                    $this->setBindTypes([$searchTermBinding => Column::BIND_PARAM_STR]);
275
                }
276
            }
277
            
278
            if (!empty($orConditions)) {
279
                $conditions [] = '(' . implode(' or ', $orConditions) . ')';
280
            }
281
        }
282
        
283
        return empty($conditions) ? null : '(' . implode(' and ', $conditions) . ')';
284
    }
285
    
286
    /**
287
     * Get Soft delete condition
288
     *
289
     * @return string Default: deleted = 0
290
     */
291
    protected function getSoftDeleteCondition()
292
    {
293
        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

293
        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...
294
    }
295
    
296
    /**
297
     * @param $field
298
     * @param string $sanitizer
299
     * @param string $glue
300
     *
301
     * @return array|string[]
302
     */
303
    public function getParamExplodeArrayMapFilter($field, $sanitizer = 'string', $glue = ',')
304
    {
305
        $filter = $this->filter;
306
        $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...
307
            return $this->appendModelName(trim($filter->sanitize($e, $sanitizer)));
308
        }, 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

308
        }, explode($glue, /** @scrutinizer ignore-type */ $this->getParam($field, $sanitizer))));
Loading history...
309
        
310
        return empty($ret) ? null : $ret;
311
    }
312
    
313
    /**
314
     * Set the variables to bind
315
     *
316
     * @param array $bind Variable bind to merge or replace
317
     * @param bool $replace Pass true to replace the entire bind set
318
     */
319
    public function setBind(array $bind = [], bool $replace = false)
320
    {
321
        $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

321
        $this->_bind = $replace ? $bind : array_merge(/** @scrutinizer ignore-type */ $this->getBind(), $bind);
Loading history...
322
    }
323
    
324
    /**
325
     * Get the current bind
326
     * key => value
327
     *
328
     * @return array|null
329
     */
330
    public function getBind()
331
    {
332
        return $this->_bind ?? null;
333
    }
334
    
335
    /**
336
     * Set the variables types to bind
337
     *
338
     * @param array $bindTypes Variable bind types to merge or replace
339
     * @param bool $replace Pass true to replace the entire bind type set
340
     */
341
    public function setBindTypes(array $bindTypes = [], bool $replace = false)
342
    {
343
        $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

343
        $this->_bindTypes = $replace ? $bindTypes : array_merge(/** @scrutinizer ignore-type */ $this->getBindTypes(), $bindTypes);
Loading history...
344
    }
345
    
346
    /**
347
     * Get the current bind types
348
     *
349
     * @return array|null
350
     */
351
    public function getBindTypes()
352
    {
353
        return $this->_bindTypes ?? null;
354
    }
355
    
356
    /**
357
     * Get Created By Condition
358
     *
359
     * @param string[] $columns
360
     * @param Identity|null $identity
361
     * @param string[]|null $roleList
362
     *
363
     * @return null
364
     *
365
     * @return string|null
366
     */
367
    protected function getIdentityCondition(array $columns = null, Identity $identity = null, $roleList = null)
368
    {
369
        $identity ??= $this->identity ?? false;
370
        $roleList ??= $this->getRoleList();
371
        $modelName = $this->getModelClassName();
372
        
373
        if ($modelName && $identity && !$identity->hasRole($roleList)) {
374
            $ret = [];
375
            
376
            $columns ??= [
377
                'createdBy',
378
                'ownedBy',
379
                'userId',
380
            ];
381
            
382
            foreach ($columns as $column) {
383
                if (!property_exists($modelName, $column)) {
384
                    continue;
385
                }
386
                
387
                $field = strpos($column, '.') !== false ? $column : $modelName . '.' . $column;
388
                $field = '[' . str_replace('.', '].[', $field) . ']';
389
                
390
                $this->setBind([$column => (int)$identity->getUserId()]);
391
                $this->setBindTypes([$column => Column::BIND_PARAM_INT]);
392
                $ret [] = $field . ' = :' . $column . ':';
393
            }
394
            
395
            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...
396
        }
397
        
398
        return null;
399
    }
400
    
401
    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...
402
    {
403
        $func = function($item) use (&$func, &$callback) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
404
            return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
405
        };
406
        
407
        return array_map($func, $array);
408
    }
409
    
410
    /**
411
     * Get Filter Condition
412
     *
413
     * @param array|null $filters
414
     * @param array|null $whiteList
415
     * @param bool $or
416
     *
417
     * @return string|null Return the generated query
418
     * @throws \Exception Throw an exception if the field property is not valid
419
     * @todo escape fields properly
420
     *
421
     */
422
    protected function getFilterCondition(array $filters = null, array $whiteList = null, $or = false)
423
    {
424
        $filters ??= $this->getParam('filters');
425
        $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...
426
        $whiteList = $this->getFlatWhiteList($whiteList);
427
        $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...
428
        
429
        // No filter, no query
430
        if (empty($filters)) {
431
            return null;
432
        }
433
        
434
        $query = [];
435
        foreach ($filters as $filter) {
436
            $field = $this->filter->sanitize($filter['field'] ?? null, ['string', 'trim']);
437
            
438
            if (!empty($field)) {
439
                $lowercaseField = mb_strtolower($field);
440
                
441
                // whiteList on filter condition
442
                if (is_null($whiteList) || !in_array($lowercaseField, $lowercaseWhiteList, true)) {
443
                    // @todo if config is set to throw exception on usage of not allowed filters otherwise continue looping through
444
                    throw new \Exception('Not allowed to filter using the following field: `' . $field . '`', 403);
445
                    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...
446
                }
447
                
448
                $uniqid = substr(md5(json_encode($filter)), 0, 10);
449
//                $queryField = '_' . uniqid($uniqid . '_field_') . '_';
450
                $queryValue = '_' . uniqid($uniqid . '_value_') . '_';
451
                $queryOperator = strtolower($filter['operator']);
452
                switch($queryOperator) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space(s) after SWITCH keyword; 0 found
Loading history...
453
                    case '=': // Equal operator
454
                    case '!=': // Not equal operator
455
                    case '<>': // Not equal operator
456
                    case '>': // Greater than operator
457
                    case '>=': // Greater than or equal operator
458
                    case '<': // Less than or equal operator
459
                    case '<=': // Less than or equal operator
460
                    case '<=>': // NULL-safe equal to operator
461
                    case 'in': // Whether a value is within a set of values
462
                    case 'not in': // Whether a value is not within a set of values
463
                    case 'like': // Simple pattern matching
464
                    case 'not like': // Negation of simple pattern matching
465
                    case 'between': // Whether a value is within a range of values
466
                    case 'not between': // Whether a value is not within a range of values
467
                    case 'is': // Test a value against a boolean
468
                    case 'is not': // Test a value against a boolean
469
                    case 'is null': // NULL value test
470
                    case 'is not null': // NOT NULL value test
471
                    case 'is false': // Test a value against a boolean
472
                    case 'is not false': // // Test a value against a boolean
473
                    case 'is true': // // Test a value against a boolean
474
                    case 'is not true': // // Test a value against a boolean
475
                        break;
476
                    default:
477
                        throw new \Exception('Not allowed to filter using the following operator: `' . $queryOperator . '`', 403);
478
                        break;
479
                }
480
                
481
                $bind = [];
482
                $bindType = [];
483
484
//                $bind[$queryField] = $filter['field'];
485
//                $bindType[$queryField] = Column::BIND_PARAM_STR;
486
//                $queryFieldBinder = ':' . $queryField . ':';
487
//                $queryFieldBinder = '{' . $queryField . '}';
488
                
489
                // Add the current model name by default
490
                $field = $this->appendModelName($field);
491
                
492
                $queryFieldBinder = $field;
493
                $queryValueBinder = ':' . $queryValue . ':';
494
                if (isset($filter['value'])) {
495
                    // special for between and not between
496
                    if (in_array($queryOperator, ['between', 'not between'])) {
497
                        $queryValue0 = '_' . uniqid($uniqid . '_value_') . '_';
498
                        $queryValue1 = '_' . uniqid($uniqid . '_value_') . '_';
499
                        $bind[$queryValue0] = $filter['value'][0];
500
                        $bind[$queryValue1] = $filter['value'][1];
501
                        $bindType[$queryValue0] = Column::BIND_PARAM_STR;
502
                        $bindType[$queryValue1] = Column::BIND_PARAM_STR;
503
                        $query [] = (($queryOperator === 'not between')? 'not ' : null) . "$queryFieldBinder between :$queryValue0: and :$queryValue1:";
504
                    }
505
                    else {
506
                        $bind[$queryValue] = $filter['value'];
507
                        if (is_string($filter['value'])) {
508
                            $bindType[$queryValue] = Column::BIND_PARAM_STR;
509
                        }
510
                        else if (is_int($filter['value'])) {
511
                            $bindType[$queryValue] = Column::BIND_PARAM_INT;
512
                        }
513
                        else if (is_bool($filter['value'])) {
514
                            $bindType[$queryValue] = Column::BIND_PARAM_BOOL;
515
                        }
516
                        else if (is_float($filter['value'])) {
517
                            $bindType[$queryValue] = Column::BIND_PARAM_DECIMAL;
518
                        }
519
                        else if (is_double($filter['value'])) {
520
                            $bindType[$queryValue] = Column::BIND_PARAM_DECIMAL;
521
                        }
522
                        else if (is_array($filter['value'])) {
523
                            $queryValueBinder = '({' . $queryValue . ':array})';
524
                            $bindType[$queryValue] = Column::BIND_PARAM_STR;
525
                        }
526
                        else {
527
                            $bindType[$queryValue] = Column::BIND_PARAM_NULL;
528
                        }
529
                        $query [] = "$queryFieldBinder $queryOperator $queryValueBinder";
530
                    }
531
                }
532
                else {
533
                    $query [] = "$queryFieldBinder $queryOperator";
534
                }
535
                
536
                $this->setBind($bind);
537
                $this->setBindTypes($bindType);
538
            }
539
            else {
540
                if (is_array($filter) || $filter instanceof \Traversable) {
541
                    $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

541
                    $query [] = $this->getFilterCondition(/** @scrutinizer ignore-type */ $filter, $whiteList, !$or);
Loading history...
542
                }
543
                else {
544
                    throw new \Exception('A valid field property is required.', 400);
545
                }
546
            }
547
        }
548
        
549
        return empty($query) ? null : '(' . implode($or ? ' or ' : ' and ', $query) . ')';
550
    }
551
    
552
    /**
553
     * Append the current model name alias to the field
554
     * So: field -> [Alias].[field]
555
     *
556
     * @param string|array $field
557
     * @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...
558
     *
559
     * @return array|string
560
     */
561
    public function appendModelName($field, $modelName = null)
562
    {
563
        $modelName ??= $this->getModelClassName();
564
        
565
        if (empty($field)) {
566
            return $field;
567
        }
568
        
569
        if (is_string($field)) {
570
            // Add the current model name by default
571
            $explode = explode(' ', $field);
572
            if (!strpos($field, '.') !== false) {
573
                $field = trim('[' . $modelName . '].[' . array_shift($explode) . '] ' . implode(' ', $explode));
574
            }
575
            else if (strpos($field, ']') === false && strpos($field, '[') === false) {
576
                $field = trim('[' . implode('].[', explode('.', array_shift($explode))) . ']' . implode(' ', $explode));
577
            }
578
        }
579
        else if (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
580
            foreach ($field as $fieldKey => $fieldValue) {
581
                $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

581
                $field[$fieldKey] = $this->appendModelName($fieldValue, /** @scrutinizer ignore-type */ $modelName);
Loading history...
582
            }
583
        }
584
        
585
        return $field;
586
    }
587
    
588
    /**
589
     * Get Permission Condition
590
     *
591
     * @return null
592
     */
593
    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

593
    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

593
    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...
594
    {
595
        return null;
596
    }
597
    
598
    /**
599
     * Get all conditions
600
     *
601
     * @return string
602
     * @throws \Exception
603
     */
604
    protected function getConditions()
605
    {
606
        $conditions = array_values(array_unique(array_filter([
607
            $this->getSoftDeleteCondition(),
608
            $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...
609
            $this->getFilterCondition(),
610
            $this->getSearchCondition(),
611
            $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...
612
        ])));
613
        
614
        return '(' . implode(') and (', $conditions) . ')';
615
    }
616
    
617
    /**
618
     * Get having conditions
619
     */
620
    public function getHaving()
621
    {
622
        return null;
623
    }
624
    
625
    /**
626
     * Get a cache key from params
627
     *
628
     * @param array|null $params
629
     *
630
     * @return string|null
631
     */
632
    public function getCacheKey(?array $params = null): ?string
633
    {
634
        $params ??= $this->getParams();
635
        
636
        return Slug::generate(json_encode($params, JSON_UNESCAPED_SLASHES));
637
    }
638
    
639
    /**
640
     * Get cache setting
641
     *
642
     * @param array|null $params
643
     *
644
     * @return array|null
645
     */
646
    public function getCache(?array $params = null)
647
    {
648
        $params ??= $this->getParams();
649
        
650
        if (!empty($params['cache'])) {
651
            return [
652
                'lifetime' => (int)$params['cache'],
653
                'key' => $this->getCacheKey($params),
654
            ];
655
        }
656
        
657
        return null;
658
    }
659
    
660
    
661
    /**
662
     * Get requested content type
663
     * - Default will return csv
664
     *
665
     * @param array|null $params
666
     *
667
     * @return string
668
     * @throws \Exception
669
     */
670
    public function getContentType(?array $params = null)
671
    {
672
        $params ??= $this->getParams();
673
        
674
        $contentType = strtolower($params['contentType'] ?? $params['content-type'] ?? 'json');
675
        
676
        switch($contentType) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space(s) after SWITCH keyword; 0 found
Loading history...
677
            case 'html':
678
            case 'text/html':
679
            case 'application/html':
680
                // html not supported yet
681
                break;
682
            case 'xml':
683
            case 'text/xml':
684
            case 'application/xml':
685
                // xml not supported yet
686
                break;
687
            case 'text':
688
            case 'text/plain':
689
                // plain text not supported yet
690
                break;
691
            case 'json':
692
            case 'text/json':
693
            case 'application/json':
694
                return 'json';
695
                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...
696
            case 'csv':
697
            case 'text/csv':
698
                return 'csv';
699
                break;
700
            case 'xlsx':
701
            case 'application/xlsx':
702
            case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
703
                return 'xlsx';
704
                break;
705
            case 'xls':
706
            case 'application/vnd.ms-excel':
707
                // old xls not supported yet
708
                break;
709
        }
710
        
711
        throw new \Exception('`' . $contentType . '` is not supported.', 400);
712
    }
713
    
714
    /**
715
     * Get find definition
716
     *
717
     * @return array
718
     * @throws \Exception
719
     */
720
    protected function getFind()
721
    {
722
        $find = [];
723
        $find['conditions'] = $this->getConditions();
724
        $find['bind'] = $this->getBind();
725
        $find['bindTypes'] = $this->getBindTypes();
726
        $find['limit'] = $this->getLimit();
727
        $find['offset'] = $this->getOffset();
728
        $find['order'] = $this->getOrder();
729
        $find['columns'] = $this->getColumns();
730
        $find['distinct'] = $this->getDistinct();
731
        $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...
732
        $find['group'] = $this->getGroup();
733
        $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...
734
        $find['cache'] = $this->getCache();
735
        
736
        // fix for grouping by multiple fields, phalcon only allow string here
737
        foreach (['distinct', 'group'] as $findKey) {
738
            if (isset($find[$findKey]) && is_array($find[$findKey])) {
739
                $find[$findKey] = implode(', ', $find[$findKey]);
740
            }
741
        }
742
        
743
        return array_filter($find);
744
    }
745
    
746
    /**
747
     * Return find lazy loading config for count
748
     * @return array|string
749
     */
750
    protected function getFindCount($find = null)
751
    {
752
        $find ??= $this->getFind();
753
        if (isset($find['limit'])) {
754
            unset($find['limit']);
755
        }
756
        if (isset($find['offset'])) {
757
            unset($find['offset']);
758
        }
759
//        if (isset($find['group'])) {
760
//            unset($find['group']);
761
//        }
762
        
763
        return array_filter($find);
764
    }
765
    
766
    /**
767
     * @param string $key
768
     * @param string[]|string|null $filters
769
     * @param string|null $default
770
     * @param array|null $params
771
     *
772
     * @return string[]|string|null
773
     */
774
    public function getParam(string $key, $filters = null, string $default = null, array $params = null)
775
    {
776
        $params ??= $this->getParams();
777
        
778
        return $this->filter->sanitize($params[$key] ?? $this->dispatcher->getParam($key, $filters, $default), $filters);
779
    }
780
    
781
    /**
782
     * Get parameters from
783
     * - JsonRawBody, post, put or get
784
     * @return mixed
785
     */
786
    protected function getParams(array $filters = null)
787
    {
788
        /** @var Request $request */
789
        $request = $this->request;
790
        
791
        if (!empty($filters)) {
792
            foreach ($filters as $filter) {
793
                $request->setParameterFilters($filter['name'], $filter['filters'], $filter['scope']);
794
            }
795
        }
796
797
//        $params = empty($request->getRawBody()) ? [] : $request->getJsonRawBody(true); // @TODO handle this differently
798
        return array_merge_recursive(
799
            $request->getFilteredQuery(), // $_GET
800
            $request->getFilteredPut(), // $_PUT
801
            $request->getFilteredPost(), // $_POST
802
        );
803
    }
804
    
805
    /**
806
     * Get Single from ID and Model Name
807
     *
808
     * @param string|int|null $id
809
     * @param string|null $modelName
810
     * @param string|array|null $with
811
     *
812
     * @return bool|Resultset
813
     */
814
    public function getSingle($id = null, $modelName = null, $with = [], $find = null, $appendCondition = true)
815
    {
816
        $id ??= (int)$this->getParam('id', 'int');
817
        $modelName ??= $this->getModelClassName();
818
        $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...
819
        $find ??= $this->getFind();
820
        $condition = '[' . $modelName . '].[id] = ' . (int)$id;
821
        if ($appendCondition) {
822
            $find['conditions'] .= (empty($find['conditions']) ? null : ' and ') . $condition;
823
        }
824
        else {
825
            $find['bind'] = [];
826
            $find['bindTypes'] = [];
827
            $find['conditions'] = $condition;
828
        }
829
        
830
        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

830
        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...
831
    }
832
    
833
    /**
834
     * Saving model automagically
835
     *
836
     * Note:
837
     * If a newly created entity can't be retrieved using the ->getSingle
838
     * method after it's creation, the entity will be returned directly
839
     *
840
     * @TODO Support Composite Primary Key
841
     *
842
     * @param null|int|string $id
843
     * @param null|\Zemit\Mvc\Model $entity
844
     * @param null|mixed $post
845
     * @param null|string $modelName
846
     * @param null|array $whiteList
847
     * @param null|array $columnMap
848
     * @param null|array $with
849
     *
850
     * @return array
851
     */
852
    protected function save($id = null, $entity = null, $post = null, $modelName = null, $whiteList = null, $columnMap = null, $with = null)
853
    {
854
        $single = false;
855
        $retList = [];
856
        
857
        // Get the model name to play with
858
        $modelName ??= $this->getModelClassName();
859
        $post ??= $this->getParams();
860
        $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...
861
        $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...
862
        $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...
863
        $id = (int)$id;
864
        
865
        // Check if multi-d post
866
        if (!empty($id) || !isset($post[0]) || !is_array($post[0])) {
867
            $single = true;
868
            $post = [$post];
869
        }
870
        
871
        // Save each posts
872
        foreach ($post as $key => $singlePost) {
873
            $ret = [];
874
            
875
            // @todo see if we should remove this earlier
876
            if (isset($singlePost['_url'])) {
877
                unset($singlePost['_url']);
878
            }
879
            
880
            $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

880
            $singlePostId = (!$single || empty($id)) ? $this->getParam('id', 'int', /** @scrutinizer ignore-type */ $this->getParam('int', 'int', null)) : $id;
Loading history...
881
            unset($singlePost['id']);
882
            
883
            /** @var \Zemit\Mvc\Model $singlePostEntity */
884
            $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

884
            $singlePostEntity = (!$single || !isset($entity)) ? $this->getSingle(/** @scrutinizer ignore-type */ $singlePostId, $modelName) : $entity;
Loading history...
885
            
886
            // Create entity if not exists
887
            if (!$singlePostEntity && empty($singlePostId)) {
888
                $singlePostEntity = new $modelName();
889
            }
890
            
891
            if (!$singlePostEntity) {
892
                $ret = [
893
                    'saved' => false,
894
                    'messages' => [new Message('Entity id `' . $singlePostId . '` not found.', $modelName, 'NotFound', 404)],
895
                    'model' => $modelName,
896
                    'source' => (new $modelName)->getSource(),
897
                ];
898
            }
899
            else {
900
                $singlePostEntity->assign($singlePost, $whiteList, $columnMap);
901
                $ret['saved'] = $singlePostEntity->save();
902
                $ret['messages'] = $singlePostEntity->getMessages();
903
                $ret['model'] = get_class($singlePostEntity);
904
                $ret['source'] = $singlePostEntity->getSource();
905
                $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...
906
                $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

906
                $fetch = $this->getSingle($singlePostEntity->/** @scrutinizer ignore-call */ getId(), $modelName, $with);
Loading history...
907
                $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

907
                $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...
908
            }
909
            
910
            $retList [] = $ret;
911
        }
912
        
913
        return $single ? $retList[0] : $retList;
914
    }
915
    
916
    /**
917
     * Try to find the appropriate model from the current controller name
918
     *
919
     * @param string $controllerName
920
     * @param array $namespaces
921
     * @param string $needle
922
     *
923
     * @return string|null
924
     */
925
    public function getModelNameFromController(string $controllerName = null, array $namespaces = null, string $needle = 'Models'): ?string
926
    {
927
        $controllerName ??= $this->dispatcher->getControllerName() ?? '';
928
        $namespaces ??= $this->loader->getNamespaces() ?? [];
929
        
930
        $model = ucfirst(Text::camelize(Text::uncamelize($controllerName)));
931
        if (!class_exists($model)) {
932
            foreach ($namespaces as $namespace => $path) {
933
                $possibleModel = $namespace . '\\' . $model;
934
                if (strpos($namespace, $needle) !== false && class_exists($possibleModel)) {
935
                    $model = $possibleModel;
936
                }
937
            }
938
        }
939
        
940
        return class_exists($model) && new $model() instanceof ModelInterface ? $model : null;
941
    }
942
    
943
    /**
944
     * Get message from list of entities
945
     *
946
     * @param $list Resultset|\Phalcon\Mvc\Model
947
     *
948
     * @return array|bool
949
     * @deprecated
950
     *
951
     */
952
    public function getRestMessages($list = null)
953
    {
954
        if (!is_array($list)) {
955
            $list = [$list];
956
        }
957
        
958
        $ret = [];
959
        
960
        foreach ($list as $single) {
961
            
962
            if ($single) {
963
                
964
                /** @var Messages $validations */
965
                $messages = $single instanceof Message ? $list : $single->getMessages();
966
                
967
                if ($messages && (is_array($messages) || $messages instanceof \Traversable)) {
968
                    
969
                    foreach ($messages as $message) {
970
                        
971
                        $validationFields = $message->getField();
972
                        
973
                        if (!is_array($validationFields)) {
974
                            $validationFields = [$validationFields];
975
                        }
976
                        
977
                        foreach ($validationFields as $validationField) {
978
                            
979
                            if (empty($ret[$validationField])) {
980
                                $ret[$validationField] = [];
981
                            }
982
                            
983
                            $ret[$validationField][] = [
984
                                'field' => $message->getField(),
985
                                'code' => $message->getCode(),
986
                                'type' => $message->getType(),
987
                                'message' => $message->getMessage(),
988
                                'metaData' => $message->getMetaData(),
989
                            ];
990
                        }
991
                    }
992
                }
993
            }
994
        }
995
        
996
        return $ret ? : false;
997
    }
998
}
999