Passed
Push — master ( 5b5c00...4c0c12 )
by Julien
04:52
created

Model::getRestMessages()   C

Complexity

Conditions 13
Paths 36

Size

Total Lines 45
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 45
ccs 0
cts 23
cp 0
rs 6.6166
c 0
b 0
f 0
cc 13
nc 36
nop 1
crap 182

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file is part of the Zemit Framework.
5
 *
6
 * (c) Zemit Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Zemit\Mvc\Controller;
13
14
use Phalcon\Db\Column;
15
use Phalcon\Messages\Message;
16
use Phalcon\Messages\Messages;
17
use Phalcon\Mvc\Model\Resultset;
18
use Phalcon\Mvc\ModelInterface;
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\Support\Exposer\Exposer;
22
use Zemit\Support\Helper;
23
use Zemit\Support\Slug;
24
25
/**
26
 * Trait Model
27
 */
28
trait Model
29
{
30
    use Params;
31
    
32
    protected $_bind = [];
33
    protected $_bindTypes = [];
34
    
35
    /**
36
     * Get the current Model Name
37
     * @return string|null
38
     * @todo remove for v1
39
     *
40
     * @deprecated change to getModelClassName() instead
41
     */
42
    public function getModelName()
43
    {
44
        return $this->getModelClassName();
45
    }
46
    
47
    /**
48
     * Get the current Model Class Name
49
     *
50
     * @return string|null|\Zemit\Mvc\Model
51
     */
52
    public function getModelClassName()
53
    {
54
        return $this->getModelNameFromController();
55
    }
56
    
57
    /**
58
     * Get the WhiteList parameters for saving
59
     * @return null|array
60
     * @todo add a whitelist object that would be able to support one configuration for the search, assign, filter
61
     *
62
     */
63
    protected function getWhiteList()
64
    {
65
        return null;
66
    }
67
    
68
    /**
69
     * Get the Flattened WhiteList
70
     *
71
     * @param array|null $whiteList
72
     *
73
     * @return array|null
74
     */
75
    public function getFlatWhiteList(?array $whiteList = null)
76
    {
77
        $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...
78
        $ret = Exposer::parseColumnsRecursive($whiteList);
79
        return $ret ? array_keys($ret) : null;
80
    }
81
    
82
    /**
83
     * Get the WhiteList parameters for filtering
84
     *
85
     * @return null|array
86
     */
87
    protected function getFilterWhiteList()
88
    {
89
        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...
90
    }
91
    
92
    /**
93
     * Get the WhiteList parameters for filtering
94
     *
95
     * @return null|array
96
     */
97
    protected function getSearchWhiteList()
98
    {
99
        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...
100
    }
101
    
102
    /**
103
     * Get the column mapping for crud
104
     *
105
     * @return null|array
106
     */
107
    protected function getColumnMap()
108
    {
109
        return null;
110
    }
111
    
112
    /**
113
     * Get relationship eager loading definition
114
     *
115
     * @return null|array
116
     */
117
    protected function getWith()
118
    {
119
        return null;
120
    }
121
    
122
    /**
123
     * Get relationship eager loading definition for a listing
124
     *
125
     * @return null|array
126
     */
127
    protected function getListWith()
128
    {
129
        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...
130
    }
131
    
132
    /**
133
     * Get relationship eager loading definition for a listing
134
     *
135
     * @return null|array
136
     */
137
    protected function getExportWith()
138
    {
139
        return $this->getListWith();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getListWith() targeting Zemit\Mvc\Controller\Model::getListWith() seems to always return null.

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

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

}

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

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

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

Loading history...
140
    }
141
    
142
    /**
143
     * Get expose definition for a single entity
144
     *
145
     * @return null|array
146
     */
147
    protected function getExpose()
148
    {
149
        return null;
150
    }
151
    
152
    /**
153
     * Get expose definition for listing many entities
154
     *
155
     * @return null|array
156
     */
157
    protected function getListExpose()
158
    {
159
        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...
160
    }
161
    
162
    /**
163
     * Get expose definition for export
164
     *
165
     * @return null|array
166
     */
167
    protected function getExportExpose()
168
    {
169
        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...
170
    }
171
    
172
    /**
173
     * Get columns merge definition for export
174
     *
175
     * @return null|array
176
     */
177
    public function getExportMergeColum()
178
    {
179
        return null;
180
    }
181
    
182
    /**
183
     * Get columns format field text definition for export
184
     *
185
     * @param array|null $params
186
     *
187
     * @return null|array
188
     */
189
    public function getExportFormatFieldText(?array $params = null)
190
    {
191
        return null;
192
    }
193
    
194
    /**
195
     * Get join definition
196
     *
197
     * @return null|array
198
     */
199
    protected function getJoins()
200
    {
201
        return null;
202
    }
203
    
204
    /**
205
     * Get the order definition
206
     *
207
     * @return null|array
208
     */
209
    protected function getOrder()
210
    {
211
        return $this->getParamExplodeArrayMapFilter('order');
212
    }
213
    
214
    /**
215
     * Get the current limit value
216
     *
217
     * @return null|int Default: 1000
218
     */
219
    protected function getLimit(): ?int
220
    {
221
        $limit = (int)$this->getParam('limit', 'int', 1000);
222
        return $limit === -1? null : abs($limit);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $limit === -1 ? null : abs($limit) could return the type double which is incompatible with the type-hinted return integer|null. Consider adding an additional type-check to rule them out.
Loading history...
223
    }
224
    
225
    /**
226
     * Get the current offset value
227
     *
228
     * @return null|int Default: 0
229
     */
230
    protected function getOffset(): int
231
    {
232
        return (int)$this->getParam('offset', 'int', 0);
233
    }
234
    
235
    /**
236
     * Get group
237
     * - Automatically group by ID by default if nothing else is provided
238
     * - This will fix multiple single records being returned for the same model with joins
239
     *
240
     * @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...
241
     */
242
    protected function getGroup()
243
    {
244
        $group = $this->getParamExplodeArrayMapFilter('group');
245
        
246
        // Fix for joins, automatically append grouping if none provided
247
        $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...
248
        if (empty($group) && !empty($join)) {
249
            $group = $this->appendModelName('id');
250
        }
251
        
252
        return $group;
253
    }
254
    
255
    /**
256
     * Get distinct
257
     * @TODO see how to implement this, maybe an action itself
258
     *
259
     * @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...
260
     */
261
    protected function getDistinct()
262
    {
263
        return $this->getParamExplodeArrayMapFilter('distinct');
264
    }
265
    
266
    /**
267
     * Get columns
268
     * @TODO see how to implement this
269
     *
270
     * @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...
271
     */
272
    protected function getColumns()
273
    {
274
        return $this->getParam('columns', 'string');
275
    }
276
    
277
    /**
278
     * Return the whitelisted role list for the current model
279
     *
280
     * @return string[] By default will return dev and admin role
281
     */
282
    protected function getRoleList()
283
    {
284
        return ['dev', 'admin'];
285
    }
286
    
287
    /**
288
     * Get Search condition
289
     *
290
     * @return string Default: deleted = 0
291
     */
292
    protected function getSearchCondition()
293
    {
294
        $conditions = [];
295
        
296
        $searchList = array_values(array_filter(array_unique(explode(' ', $this->getParam('search', 'string') ?? ''))));
297
        
298
        foreach ($searchList as $searchTerm) {
299
            $orConditions = [];
300
            $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...
301
            if ($searchWhiteList) {
302
                foreach ($searchWhiteList as $whiteList) {
0 ignored issues
show
Bug introduced by
The expression $searchWhiteList of type void is not traversable.
Loading history...
303
                    
304
                    // Multidimensional arrays not supported yet
305
                    // @todo support this properly
306
                    if (is_array($whiteList)) {
307
                        continue;
308
                    }
309
                    
310
                    $searchTermBinding = '_' . uniqid() . '_';
311
                    $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

311
                    $orConditions [] = /** @scrutinizer ignore-type */ $this->appendModelName($whiteList) . " like :$searchTermBinding:";
Loading history...
312
                    $this->setBind([$searchTermBinding => '%' . $searchTerm . '%']);
313
                    $this->setBindTypes([$searchTermBinding => Column::BIND_PARAM_STR]);
314
                }
315
            }
316
            
317
            if (!empty($orConditions)) {
318
                $conditions [] = '(' . implode(' or ', $orConditions) . ')';
319
            }
320
        }
321
        
322
        return empty($conditions) ? null : '(' . implode(' and ', $conditions) . ')';
323
    }
324
    
325
    /**
326
     * Get Soft delete condition
327
     *
328
     * @return string Default: deleted = 0
329
     */
330
    protected function getSoftDeleteCondition(): ?string
331
    {
332
        return '[' . $this->getModelClassName() . '].[deleted] = 0';
333
    }
334
    
335
    /**
336
     * This function will explode the field based using the glue
337
     * and sanitize the values and append the model name
338
     */
339
    public function getParamExplodeArrayMapFilter(string $field, string $sanitizer = 'string', string $glue = ',', int $limit = PHP_INT_MAX) : ?array
340
    {
341
        $ret = [];
342
        $filter = $this->filter;
0 ignored issues
show
Bug introduced by
The property filter does not exist on Zemit\Mvc\Controller\Model. Did you mean filter;?
Loading history...
343
        $params = $this->getParam($field, $sanitizer);
344
        foreach (is_array($params)? $params : [$params] as $param) {
345
            $ret = array_filter(array_merge($ret, array_map(function ($e) use ($filter, $sanitizer) {
346
                return strrpos(strtoupper($e), 'RAND()') === 0? $e : $this->appendModelName(trim($filter->sanitize($e, $sanitizer)));
347
            }, explode($glue, $param ?? '', $limit))));
348
        }
349
        
350
        return empty($ret) ? null : $ret;
351
    }
352
    
353
    /**
354
     * Set the variables to bind
355
     *
356
     * @param array $bind Variable bind to merge or replace
357
     * @param bool $replace Pass true to replace the entire bind set
358
     */
359
    public function setBind(array $bind = [], bool $replace = false)
360
    {
361
        $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

361
        $this->_bind = $replace ? $bind : array_merge(/** @scrutinizer ignore-type */ $this->getBind(), $bind);
Loading history...
362
    }
363
    
364
    /**
365
     * Get the current bind
366
     * key => value
367
     *
368
     * @return array|null
369
     */
370
    public function getBind()
371
    {
372
        return $this->_bind ?? null;
373
    }
374
    
375
    /**
376
     * Set the variables types to bind
377
     *
378
     * @param array $bindTypes Variable bind types to merge or replace
379
     * @param bool $replace Pass true to replace the entire bind type set
380
     */
381
    public function setBindTypes(array $bindTypes = [], bool $replace = false)
382
    {
383
        $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

383
        $this->_bindTypes = $replace ? $bindTypes : array_merge(/** @scrutinizer ignore-type */ $this->getBindTypes(), $bindTypes);
Loading history...
384
    }
385
    
386
    /**
387
     * Get the current bind types
388
     *
389
     * @return array|null
390
     */
391
    public function getBindTypes()
392
    {
393
        return $this->_bindTypes ?? null;
394
    }
395
    
396
    /**
397
     * Get Created By Condition
398
     *
399
     * @param string[] $columns
400
     * @param Identity|null $identity
401
     * @param string[]|null $roleList
402
     *
403
     * @return null
404
     *
405
     * @return string|null
406
     */
407
    protected function getIdentityCondition(array $columns = null, Identity $identity = null, $roleList = null)
408
    {
409
        $identity ??= $this->identity ?? false;
0 ignored issues
show
Bug introduced by
The property identity does not exist on Zemit\Mvc\Controller\Model. Did you mean identity;?
Loading history...
410
        $roleList ??= $this->getRoleList();
411
        $modelName = $this->getModelClassName();
412
        
413
        if ($modelName && $identity && !$identity->hasRole($roleList)) {
414
            $ret = [];
415
            
416
            $columns ??= [
417
                'createdBy',
418
                'ownedBy',
419
                'userId',
420
            ];
421
            
422
            foreach ($columns as $column) {
423
                if (!property_exists($modelName, $column)) {
424
                    continue;
425
                }
426
                
427
                $field = strpos($column, '.') !== false ? $column : $modelName . '.' . $column;
428
                $field = '[' . str_replace('.', '].[', $field) . ']';
429
                
430
                $this->setBind([$column => (int)$identity->getUserId()]);
431
                $this->setBindTypes([$column => Column::BIND_PARAM_INT]);
432
                $ret [] = $field . ' = :' . $column . ':';
433
            }
434
            
435
            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...
436
        }
437
        
438
        return null;
439
    }
440
    
441
    public function arrayMapRecursive(callable $callback, array $array): array
442
    {
443
        $func = function ($item) use (&$func, &$callback) {
444
            return is_array($item) ? array_map($func, $item) : call_user_func($callback, $item);
445
        };
446
        
447
        return array_map($func, $array);
448
    }
449
    
450
    /**
451
     * Get Filter Condition
452
     *
453
     * @param array|null $filters
454
     * @param array|null $whiteList
455
     * @param bool $or
456
     *
457
     * @return string|null Return the generated query
458
     * @throws \Exception Throw an exception if the field property is not valid
459
     * @todo escape fields properly
460
     *
461
     */
462
    protected function getFilterCondition(array $filters = null, array $whiteList = null, bool $or = false)
463
    {
464
        $filters ??= $this->getParam('filters');
465
        $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...
466
        $whiteList = $this->getFlatWhiteList($whiteList);
467
        $lowercaseWhiteList = !is_null($whiteList) ? $this->arrayMapRecursive('mb_strtolower', $whiteList) : $whiteList;
468
        
469
        // No filter, no query
470
        if (empty($filters)) {
471
            return null;
472
        }
473
        
474
        $query = [];
475
        foreach ($filters as $filter) {
476
            $field = $this->filter->sanitize($filter['field'] ?? null, ['string', 'trim']);
0 ignored issues
show
Bug introduced by
The property filter does not exist on Zemit\Mvc\Controller\Model. Did you mean filter;?
Loading history...
477
            
478
            // @todo logic bitwise operator
479
            $logic = $this->filter->sanitize($filter['logic'] ?? null, ['string', 'trim', 'lower']);
480
            $logic = $logic ?: ($or ? 'or' : 'and');
481
            $logic = ' ' . $logic . ' ';
0 ignored issues
show
Unused Code introduced by
The assignment to $logic is dead and can be removed.
Loading history...
482
            
483
            if (!empty($field)) {
484
                $lowercaseField = mb_strtolower($field);
485
                
486
                // whiteList on filter condition
487
                if (is_null($whiteList) || !in_array($lowercaseField, $lowercaseWhiteList ?? [], true)) {
488
                    // @todo if config is set to throw exception on usage of not allowed filters otherwise continue looping through
489
                    throw new \Exception('Not allowed to filter using the following field: `' . $field . '`', 403);
490
                }
491
                
492
                $uniqid = substr(md5(json_encode($filter)), 0, 10);
493
//                $queryField = '_' . uniqid($uniqid . '_field_') . '_';
494
                $queryValue = '_' . uniqid($uniqid . '_value_') . '_';
495
                $queryOperator = strtolower($filter['operator']);
496
                
497
                // Map alias query operator
498
                $mapAlias = [
499
                    'equals' => '=',
500
                    'not equal' => '!=',
501
                    'does not equal' => '!=',
502
                    'different than' => '<>',
503
                    'greater than' => '>',
504
                    'greater than or equal' => '>=',
505
                    'less than' => '<',
506
                    'less than or equal' => '<=',
507
                    'null-safe equal' => '<=>',
508
                ];
509
                $queryOperator = $mapAlias[$queryOperator] ?? $queryOperator;
510
                
511
                switch ($queryOperator) {
512
                    
513
                    // mysql native
514
                    case '=': // Equal operator
515
                    case '!=': // Not equal operator
516
                    case '<>': // Not equal operator
517
                    case '>': // Greater than operator
518
                    case '>=': // Greater than or equal operator
519
                    case '<': // Less than or equal operator
520
                    case '<=': // Less than or equal operator
521
                    case '<=>': // NULL-safe equal to operator
522
                    case 'in': // Whether a value is within a set of values
523
                    case 'not in': // Whether a value is not within a set of values
524
                    case 'like': // Simple pattern matching
525
                    case 'not like': // Negation of simple pattern matching
526
                    case 'between': // Whether a value is within a range of values
527
                    case 'not between': // Whether a value is not within a range of values
528
                    case 'is': // Test a value against a boolean
529
                    case 'is not': // Test a value against a boolean
530
                    case 'is null': // NULL value test
531
                    case 'is not null': // NOT NULL value test
532
                    case 'is false': // Test a value against a boolean
533
                    case 'is not false': // // Test a value against a boolean
534
                    case 'is true': // // Test a value against a boolean
535
                    case 'is not true': // // Test a value against a boolean
536
                        break;
537
                    
538
                    // advanced filters
539
                    case 'start with':
540
                    case 'does not start with':
541
                    case 'end with':
542
                    case 'does not end with':
543
                    case 'regexp':
544
                    case 'not regexp':
545
                    case 'contains':
546
                    case 'does not contain':
547
                    case 'contains word':
548
                    case 'does not contain word':
549
                    case 'distance sphere greater than':
550
                    case 'distance sphere greater than or equal':
551
                    case 'distance sphere less than':
552
                    case 'distance sphere less than or equal':
553
                    case 'is empty':
554
                    case 'is not empty':
555
                        break;
556
                    
557
                    default:
558
                        throw new \Exception('Not allowed to filter using the following operator: `' . $queryOperator . '`', 403);
559
                }
560
                
561
                $bind = [];
562
                $bindType = [];
563
564
//                $bind[$queryField] = $filter['field'];
565
//                $bindType[$queryField] = Column::BIND_PARAM_STR;
566
//                $queryFieldBinder = ':' . $queryField . ':';
567
//                $queryFieldBinder = '{' . $queryField . '}';
568
                
569
                // Add the current model name by default
570
                $field = $this->appendModelName($field);
571
                
572
                $queryFieldBinder = $field;
573
                $queryValueBinder = ':' . $queryValue . ':';
574
                if (isset($filter['value'])) {
575
                    
576
                    
577
                    // special for between and not between
578
                    if (in_array($queryOperator, ['between', 'not between'])) {
579
                        $queryValue0 = '_' . uniqid($uniqid . '_value_') . '_';
580
                        $queryValue1 = '_' . uniqid($uniqid . '_value_') . '_';
581
                        
582
                        $queryValueIndex = $filter['value'][0] <= $filter['value'][1]? 0 : 1;
583
                        $bind[$queryValue0] = $filter['value'][$queryValueIndex? 1 : 0];
584
                        $bind[$queryValue1] = $filter['value'][$queryValueIndex? 0 : 1];
585
                        
586
                        $bindType[$queryValue0] = Column::BIND_PARAM_STR;
587
                        $bindType[$queryValue1] = Column::BIND_PARAM_STR;
588
                        
589
                        $query [] = (($queryOperator === 'not between') ? 'not ' : null) . "$queryFieldBinder between :$queryValue0: and :$queryValue1:";
590
                    }
591
                    
592
                    elseif (in_array($queryOperator, [
593
                            'distance sphere equals',
594
                            'distance sphere greater than',
595
                            'distance sphere greater than or equal',
596
                            'distance sphere less than',
597
                            'distance sphere less than or equal',
598
                        ])
599
                    ) {
600
                        // Prepare values binding of 2 sphere point to calculate distance
601
                        $queryBindValue0 = '_' . uniqid($uniqid . '_value_') . '_';
602
                        $queryBindValue1 = '_' . uniqid($uniqid . '_value_') . '_';
603
                        $queryBindValue2 = '_' . uniqid($uniqid . '_value_') . '_';
604
                        $queryBindValue3 = '_' . uniqid($uniqid . '_value_') . '_';
605
                        $bind[$queryBindValue0] = $filter['value'][0];
606
                        $bind[$queryBindValue1] = $filter['value'][1];
607
                        $bind[$queryBindValue2] = $filter['value'][2];
608
                        $bind[$queryBindValue3] = $filter['value'][3];
609
                        $bindType[$queryBindValue0] = Column::BIND_PARAM_DECIMAL;
610
                        $bindType[$queryBindValue1] = Column::BIND_PARAM_DECIMAL;
611
                        $bindType[$queryBindValue2] = Column::BIND_PARAM_DECIMAL;
612
                        $bindType[$queryBindValue3] = Column::BIND_PARAM_DECIMAL;
613
                        $queryPointLatBinder0 = ':' . $queryBindValue0 . ':';
614
                        $queryPointLonBinder0 = ':' . $queryBindValue1 . ':';
615
                        $queryPointLatBinder1 = ':' . $queryBindValue2 . ':';
616
                        $queryPointLonBinder1 = ':' . $queryBindValue3 . ':';
617
                        $queryLogicalOperator =
618
                            (strpos($queryOperator, 'greater') !== false ? '>' : null) .
619
                            (strpos($queryOperator, 'less') !== false ? '<' : null) .
620
                            (strpos($queryOperator, 'equal') !== false ? '=' : null);
621
                        
622
                        $bind[$queryValue] = $filter['value'];
623
                        $query [] = "ST_Distance_Sphere(point($queryPointLatBinder0, $queryPointLonBinder0), point($queryPointLatBinder1, $queryPointLonBinder1)) $queryLogicalOperator $queryValueBinder";
624
                    }
625
                    
626
                    elseif (in_array($queryOperator, [
627
                            'in',
628
                            'not in',
629
                        ])
630
                    ) {
631
                        $queryValueBinder = '({' . $queryValue . ':array})';
632
                        $bind[$queryValue] = $filter['value'];
633
                        $bindType[$queryValue] = Column::BIND_PARAM_STR;
634
                        $query [] = "$queryFieldBinder $queryOperator $queryValueBinder";
635
                    }
636
                    
637
                    else {
638
                        $queryAndOr = [];
639
                        
640
                        $valueList = is_array($filter['value']) ? $filter['value'] : [$filter['value']];
641
                        foreach ($valueList as $value) {
642
                            
643
                            $queryValue = '_' . uniqid($uniqid . '_value_') . '_';
644
                            $queryValueBinder = ':' . $queryValue . ':';
645
                            
646
                            if (in_array($queryOperator, ['contains', 'does not contain'])) {
647
                                $queryValue0 = '_' . uniqid($uniqid . '_value_') . '_';
648
                                $queryValue1 = '_' . uniqid($uniqid . '_value_') . '_';
649
                                $queryValue2 = '_' . uniqid($uniqid . '_value_') . '_';
650
                                $bind[$queryValue0] = '%' . $value . '%';
651
                                $bind[$queryValue1] = '%' . $value;
652
                                $bind[$queryValue2] = $value . '%';
653
                                $bindType[$queryValue0] = Column::BIND_PARAM_STR;
654
                                $bindType[$queryValue1] = Column::BIND_PARAM_STR;
655
                                $bindType[$queryValue2] = Column::BIND_PARAM_STR;
656
                                $queryAndOr [] = ($queryOperator === 'does not contain' ? '!' : '') . "($queryFieldBinder like :$queryValue0: or $queryFieldBinder like :$queryValue1: or $queryFieldBinder like :$queryValue2:)";
657
                            }
658
                            
659
                            elseif (in_array($queryOperator, ['starts with', 'does not start with'])) {
660
                                $bind[$queryValue] = $value . '%';
661
                                $bindType[$queryValue] = Column::BIND_PARAM_STR;
662
                                $queryAndOr [] = ($queryOperator === 'does not start with' ? '!' : '') . "($queryFieldBinder like :$queryValue:)";
663
                            }
664
                            
665
                            elseif (in_array($queryOperator, ['ends with', 'does not end with'])) {
666
                                $bind[$queryValue] = '%' . $value;
667
                                $bindType[$queryValue] = Column::BIND_PARAM_STR;
668
                                $queryAndOr [] = ($queryOperator === 'does not end with' ? '!' : '') . "($queryFieldBinder like :$queryValue:)";
669
                            }
670
                            
671
                            elseif (in_array($queryOperator, ['is empty', 'is not empty'])) {
672
                                $queryAndOr [] = ($queryOperator === 'is not empty' ? '!' : '') . "(TRIM($queryFieldBinder) = '' or $queryFieldBinder is null)";
673
                            }
674
                            
675
                            elseif (in_array($queryOperator, ['regexp', 'not regexp'])) {
676
                                $bind[$queryValue] = $value;
677
                                $queryAndOr [] = $queryOperator . "($queryFieldBinder, :$queryValue:)";
678
                            }
679
                            
680
                            elseif (in_array($queryOperator, ['contains word', 'does not contain word'])) {
681
                                $bind[$queryValue] = '\\b' . $value . '\\b';
682
                                $regexQueryOperator = str_replace(['contains word', 'does not contain word'], ['regexp', 'not regexp'], $queryOperator);
683
                                $queryAndOr [] = $regexQueryOperator . "($queryFieldBinder, :$queryValue:)";
684
                            }
685
                            
686
                            else {
687
                                $bind[$queryValue] = $value;
688
                                
689
                                if (is_string($value)) {
690
                                    $bindType[$queryValue] = Column::BIND_PARAM_STR;
691
                                }
692
                                
693
                                elseif (is_int($value)) {
694
                                    $bindType[$queryValue] = Column::BIND_PARAM_INT;
695
                                }
696
                                
697
                                elseif (is_bool($value)) {
698
                                    $bindType[$queryValue] = Column::BIND_PARAM_BOOL;
699
                                }
700
                                
701
                                elseif (is_float($value)) {
702
                                    $bindType[$queryValue] = Column::BIND_PARAM_DECIMAL;
703
                                }
704
                                
705
                                elseif (is_double($value)) {
706
                                    $bindType[$queryValue] = Column::BIND_PARAM_DECIMAL;
707
                                }
708
                                
709
                                elseif (is_array($value)) {
710
                                    $queryValueBinder = '({' . $queryValue . ':array})';
711
                                    $bindType[$queryValue] = Column::BIND_PARAM_STR;
712
                                }
713
                                
714
                                else {
715
                                    $bindType[$queryValue] = Column::BIND_PARAM_NULL;
716
                                }
717
                                
718
                                $queryAndOr [] = "$queryFieldBinder $queryOperator $queryValueBinder";
719
                            }
720
                        }
721
                        if (!empty($queryAndOr)) {
722
                            $andOr = str_contains($queryOperator, ' not ')? 'and' : 'or';
723
                            $query [] = '((' . implode(') ' . $andOr . ' (', $queryAndOr) . '))';
724
                        }
725
                    }
726
                }
727
                else {
728
                    $query [] = "$queryFieldBinder $queryOperator";
729
                }
730
                
731
                $this->setBind($bind);
732
                $this->setBindTypes($bindType);
733
            }
734
            elseif (is_array($filter)) {
735
                $query [] = $this->getFilterCondition($filter, $whiteList, !$or);
736
            }
737
            else {
738
                throw new \Exception('A valid field property is required.', 400);
739
            }
740
        }
741
        
742
        return empty($query) ? null : '(' . implode($or ? ' or ' : ' and ', $query) . ')';
743
    }
744
    
745
    /**
746
     * Append the current model name alias to the field
747
     * So: field -> [Alias].[field]
748
     *
749
     * @param string|array $field
750
     * @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...
751
     *
752
     * @return array|string
753
     */
754
    public function appendModelName($field, $modelName = null)
755
    {
756
        $modelName ??= $this->getModelClassName();
757
        
758
        if (empty($field)) {
759
            return $field;
760
        }
761
        
762
        if (is_string($field)) {
763
            // Add the current model name by default
764
            $explode = explode(' ', $field);
765
            if (!strpos($field, '.') !== false) {
766
                $field = trim('[' . $modelName . '].[' . array_shift($explode) . '] ' . implode(' ', $explode));
767
            }
768
            elseif (strpos($field, ']') === false && strpos($field, '[') === false) {
769
                $field = trim('[' . implode('].[', explode('.', array_shift($explode))) . ']' . implode(' ', $explode));
770
            }
771
        }
772
        elseif (is_array($field)) {
0 ignored issues
show
introduced by
The condition is_array($field) is always true.
Loading history...
773
            foreach ($field as $fieldKey => $fieldValue) {
774
                $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

774
                $field[$fieldKey] = $this->appendModelName($fieldValue, /** @scrutinizer ignore-type */ $modelName);
Loading history...
775
            }
776
        }
777
        
778
        return $field;
779
    }
780
    
781
    /**
782
     * Get Permission Condition
783
     *
784
     * @return null
785
     */
786
    protected function getPermissionCondition($type = null, $identity = null)
787
    {
788
        return null;
789
    }
790
    
791
    protected function fireGet($method)
792
    {
793
        // @todo
794
//        $eventRet = $this->eventsManager->fire('rest:before' . ucfirst($method), $this);
795
//        if ($eventRet !== false) {
796
//            $ret = $this->{$method}();
797
//            $eventRet = $this->eventsManager->fire('rest:after' . ucfirst($method), $this, $ret);
798
//        }
799
        
800
        $ret = $this->{$method}();
801
        $eventRet = $this->eventsManager->fire('rest:' . $method, $this, $ret);
0 ignored issues
show
Bug introduced by
The property eventsManager does not exist on Zemit\Mvc\Controller\Model. Did you mean eventsManager;?
Loading history...
802
        return $eventRet === false ? null : $eventRet ?? $ret;
803
    }
804
    
805
    /**
806
     * Get all conditions
807
     *
808
     * @return string
809
     * @throws \Exception
810
     */
811
    protected function getConditions()
812
    {
813
        $conditions = array_values(array_unique(array_filter([
814
            $this->fireGet('getSoftDeleteCondition'),
815
            $this->fireGet('getIdentityCondition'),
816
            $this->fireGet('getFilterCondition'),
817
            $this->fireGet('getSearchCondition'),
818
            $this->fireGet('getPermissionCondition'),
819
        ])));
820
        
821
        if (empty($conditions)) {
822
            $conditions [] = 1;
823
        }
824
        
825
        return '(' . implode(') and (', $conditions) . ')';
826
    }
827
    
828
    /**
829
     * Get having conditions
830
     */
831
    public function getHaving()
832
    {
833
        return null;
834
    }
835
    
836
    /**
837
     * Get a cache key from params
838
     *
839
     * @param array|null $params
840
     *
841
     * @return string|null
842
     */
843
    public function getCacheKey(?array $params = null): ?string
844
    {
845
        $params ??= $this->getParams();
846
        
847
        return Slug::generate(json_encode($params, JSON_UNESCAPED_SLASHES));
848
    }
849
    
850
    /**
851
     * Get cache setting
852
     *
853
     * @param array|null $params
854
     *
855
     * @return array|null
856
     */
857
    public function getCache(?array $params = null)
858
    {
859
        $params ??= $this->getParams();
860
        
861
        if (!empty($params['cache'])) {
862
            return [
863
                'lifetime' => (int)$params['cache'],
864
                'key' => $this->getCacheKey($params),
865
            ];
866
        }
867
        
868
        return null;
869
    }
870
    
871
    /**
872
     * Get find definition
873
     *
874
     * @return array
875
     * @throws \Exception
876
     */
877
    protected function getFind()
878
    {
879
        $find = [];
880
        $find['conditions'] = $this->fireGet('getConditions');
881
        $find['bind'] = $this->fireGet('getBind');
882
        $find['bindTypes'] = $this->fireGet('getBindTypes');
883
        $find['limit'] = $this->fireGet('getLimit');
884
        $find['offset'] = $this->fireGet('getOffset');
885
        $find['order'] = $this->fireGet('getOrder');
886
        $find['columns'] = $this->fireGet('getColumns');
887
        $find['distinct'] = $this->fireGet('getDistinct');
888
        $find['joins'] = $this->fireGet('getJoins');
889
        $find['group'] = $this->fireGet('getGroup');
890
        $find['having'] = $this->fireGet('getHaving');
891
        $find['cache'] = $this->fireGet('getCache');
892
        
893
        // fix for grouping by multiple fields, phalcon only allow string here
894
        foreach (['distinct', 'group', 'order'] as $findKey) {
895
            if (isset($find[$findKey]) && is_array($find[$findKey])) {
896
                $find[$findKey] = implode(', ', $find[$findKey]);
897
            }
898
        }
899
        
900
        return array_filter($find);
901
    }
902
    
903
    /**
904
     * Return find lazy loading config for count
905
     * @return array|string
906
     */
907
    protected function getFindCount($find = null)
908
    {
909
        $find ??= $this->getFind();
910
        if (isset($find['limit'])) {
911
            unset($find['limit']);
912
        }
913
        if (isset($find['offset'])) {
914
            unset($find['offset']);
915
        }
916
//        if (isset($find['group'])) {
917
//            unset($find['group']);
918
//        }
919
        
920
        return array_filter($find);
921
    }
922
    
923
    /**
924
     * Get Single from ID and Model Name
925
     *
926
     * @param string|int|null $id
927
     * @param string|null $modelName
928
     * @param string|array|null $with
929
     *
930
     * @return bool|Resultset|\Zemit\Mvc\Model
931
     */
932
    public function getSingle($id = null, $modelName = null, $with = [], $find = null, $appendCondition = true)
933
    {
934
        $id ??= (int)$this->getParam('id', 'int');
935
        $modelName ??= $this->getModelClassName();
936
        $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...
937
        $find ??= $this->getFind();
938
        
939
        $condition = '[' . $modelName . '].[id] = ' . (int)$id;
940
        
941
        if ($appendCondition) {
942
            $find['conditions'] .= (empty($find['conditions']) ? null : ' and ') . $condition;
943
        }
944
        else {
945
            $find['bind'] = [];
946
            $find['bindTypes'] = [];
947
            $find['conditions'] = $condition;
948
        }
949
        
950
        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

950
        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...
951
    }
952
    
953
    /**
954
     * Saving model automagically
955
     *
956
     * Note:
957
     * If a newly created entity can't be retrieved using the ->getSingle
958
     * method after it's creation, the entity will be returned directly
959
     *
960
     * @TODO Support Composite Primary Key*
961
     */
962
    protected function save(?int $id = null, ?ModelInterface $entity = null, ?array $post = null, ?string $modelName = null, ?array $whiteList = null, ?array $columnMap = null, ?array $with = null): array
963
    {
964
        $single = false;
965
        $retList = [];
966
        
967
        // Get the model name to play with
968
        $modelName ??= $this->getModelClassName();
969
        $post ??= $this->getParams();
970
        $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...
971
        $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...
972
        $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...
973
        $id = (int)$id;
974
        
975
        // Check if multi-d post
976
        if (!empty($id) || !isset($post[0]) || !is_array($post[0])) {
977
            $single = true;
978
            $post = [$post];
979
        }
980
        
981
        // Save each posts
982
        foreach ($post as $key => $singlePost) {
983
            $singlePostId = (!$single || empty($id)) ? $this->getParam('id', 'int', $this->getParam('int', 'int', $singlePost['id'] ?? null)) : $id;
984
            if (isset($singlePost['id'])) {
985
                unset($singlePost['id']);
986
            }
987
            
988
            /** @var \Zemit\Mvc\Model $singlePostEntity */
989
            $singlePostEntity = (!$single || !isset($entity)) ? $this->getSingle($singlePostId, $modelName, []) : $entity;
990
            
991
            // Create entity if not exists
992
            if (!$singlePostEntity && empty($singlePostId)) {
993
                $singlePostEntity = new $modelName();
994
            }
995
            
996
            if (!$singlePostEntity) {
997
                $ret = [
998
                    'saved' => false,
999
                    'messages' => [new Message('Entity id `' . $singlePostId . '` not found.', $modelName, 'NotFound', 404)],
1000
                    'model' => $modelName,
1001
                    'source' => (new $modelName())->getSource(),
1002
                ];
1003
            }
1004
            else {
1005
                // allow custom manipulations
1006
                // @todo move this using events
1007
                $this->beforeAssign($singlePostEntity, $singlePost, $whiteList, $columnMap);
1008
                
1009
                // assign & save
1010
                $singlePostEntity->assign($singlePost, $whiteList, $columnMap);
1011
                $ret = $this->saveEntity($singlePostEntity);
1012
                
1013
                // refetch & expose
1014
//                $fetchWith = $this->getSingle($singlePostEntity->getId(), $modelName, $with);
1015
//                $ret['single'] = $this->expose($fetchWith);
1016
                $fetchWith = $singlePostEntity->load($with ?? []);
1017
                $ret['single'] = $this->expose($fetchWith);
0 ignored issues
show
Bug introduced by
It seems like expose() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

1017
                /** @scrutinizer ignore-call */ 
1018
                $ret['single'] = $this->expose($fetchWith);
Loading history...
1018
            }
1019
            
1020
            if ($single) {
1021
                return $ret;
1022
            }
1023
            else {
1024
                $retList [] = $ret;
1025
            }
1026
        }
1027
        
1028
        return $retList;
1029
    }
1030
    
1031
    /**
1032
     * Allow overrides to add alter variables before entity assign & save
1033
     */
1034
    public function beforeAssign(ModelInterface &$entity, array &$post, ?array &$whiteList, ?array &$columnMap): void
1035
    {
1036
    }
1037
    
1038
    /**
1039
     * Save an entity and return an array of the result
1040
     *
1041
     */
1042
    public function saveEntity(ModelInterface $entity): array
1043
    {
1044
        $ret = [];
1045
        
1046
        $ret['saved'] = $entity->save();
1047
        $ret['messages'] = $entity->getMessages();
1048
        $ret['model'] = get_class($entity);
1049
        $ret['source'] = $entity->getSource();
1050
        $ret['entity'] = $entity; // @todo this is to fix a phalcon internal bug (503 segfault during eagerload)
1051
        $ret['single'] = $this->expose($entity, $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...
1052
        
1053
        return $ret;
1054
    }
1055
    
1056
    /**
1057
     * Try to find the appropriate model from the current controller name
1058
     *
1059
     * @param ?string $controllerName
1060
     * @param ?array $namespaces
1061
     * @param string $needle
1062
     *
1063
     * @return string|null|\Zemit\Mvc\Model
1064
     */
1065
    public function getModelNameFromController(string $controllerName = null, array $namespaces = null, string $needle = 'Models\\'): ?string
1066
    {
1067
        $controllerName ??= $this->dispatcher->getControllerName() ?? '';
0 ignored issues
show
Bug introduced by
The property dispatcher does not exist on Zemit\Mvc\Controller\Model. Did you mean dispatcher;?
Loading history...
1068
        $namespaces ??= $this->loader->getNamespaces() ?? [];
1069
        
1070
        $model = ucfirst(Helper::camelize(Helper::uncamelize($controllerName)));
1071
        if (!class_exists($model)) {
1072
            foreach ($namespaces as $namespace => $path) {
1073
                $possibleModel = preg_replace('/\\\\+/', '\\', $namespace . '\\' . $model); // @todo check if phalcon namespaces are always going to end with a trailing backslash
1074
                if (str_ends_with($namespace, $needle) && class_exists($possibleModel)) {
1075
                    $model = $possibleModel;
1076
                }
1077
            }
1078
        }
1079
        
1080
        return class_exists($model) && new $model() instanceof ModelInterface ? $model : null;
1081
    }
1082
}
1083