Filtered::applyFieldFilter()   B
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 25
Code Lines 11

Duplication

Lines 25
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 25
loc 25
rs 8.5806
cc 4
eloc 11
nc 5
nop 1
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: egorov
5
 * Date: 26.12.2014
6
 * Time: 16:22
7
 */
8
namespace samsoncms\collection;
9
10
use samson\activerecord\Condition;
11
use samson\activerecord\dbRelation;
12
use samsonframework\collection\Paged;
13
use samsonframework\pager\PagerInterface;
14
use samsonframework\core\RenderInterface;
15
use samsonframework\orm\QueryInterface;
16
17
/**
18
 * Collection query builder for filtering
19
 * @package samsonos\cms\collection
20
 * @author Egorov Vitaly <[email protected]>
21
 */
22
class Filtered extends Paged
23
{
24
    /** @var array Collection for current filtered material identifiers */
25
    protected $materialIDs = array();
26
27
    /** @var array Collection of navigation filters */
28
    protected $navigation = array();
29
30
    /** @var array Collection of field filters */
31
    protected $field = array();
32
33
    /** @var array Search string collection */
34
    protected $search = array();
35
36
    /** @var array Collection of query handlers */
37
    protected $idHandlers = array();
38
39
    /** @var array External material handler and params array */
40
    protected $entityHandlers = array();
41
42
    /** @var array Base material entity handler callbacks array */
43
    protected $baseEntityHandlers = array();
44
45
    /** @var string Collection entities class name */
46
    protected $entityName = 'samson\cms\CMSMaterial';
47
48
    /** @var array Sorter parameters collection */
49
    protected $sorter = array();
50
51
    /**
52
     * Generic collection constructor
53
     * @param RenderInterface $renderer View render object
54
     * @param QueryInterface $query Query object
55
     */
56
    public function __construct(RenderInterface $renderer, QueryInterface $query, PagerInterface $pager)
57
    {
58
        // Call parent initialization
59
        parent::__construct($renderer, $query->className('material'), $pager);
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
60
    }
61
62
    /**
63
     * Render products collection block
64
     * @param string $prefix Prefix for view variables
65
     * @param array $restricted Collection of ignored keys
66
     * @return array Collection key => value
67
     */
68
    public function toView($prefix = null, array $restricted = array())
69
    {
70
        // Render pager and collection
71
        return array(
72
            $prefix.'html' => $this->render(),
73
            $prefix.'pager' => $this->pager->total > 1 ? $this->pager->toHTML() : ''
0 ignored issues
show
Bug introduced by
Accessing total on the interface samsonframework\pager\PagerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
74
        );
75
    }
76
77
    /**
78
     * Add external identifier filter handler
79
     * @param callback $handler
80
     * @param array $params
81
     * @return self Chaining
82
     */
83
    public function handler($handler, array $params = array())
84
    {
85
        // Add callback with parameters to array
86
        $this->idHandlers[] = array($handler, $params);
87
88
        return $this;
89
    }
90
91
    /**
92
     * Set external entity handler
93
     * @param callback $handler
94
     * @param array $params
95
     * @return self Chaining
96
     */
97
    public function baseEntityHandler($handler, array $params = array())
98
    {
99
        // Add callback with parameters to array
100
        $this->baseEntityHandlers[] = array($handler, $params);
101
102
        return $this;
103
    }
104
105
    /**
106
     * Set external entity handler
107
     * @param callback $handler
108
     * @param array $params
109
     * @return self Chaining
110
     */
111
    public function entityHandler($handler, array $params = array())
112
    {
113
        // Add callback with parameters to array
114
        $this->entityHandlers[] = array($handler, $params);
115
116
        return $this;
117
    }
118
119
    /**
120
     * Set collection sorter parameters
121
     * @param string|integer $field Field identifier or name
122
     * @param string $destination ASC|DESC
123
     * @return void
124
     */
125
    public function sorter($field, $destination = 'ASC')
126
    {
127
        /**@var \samson\activerecord\field $field */
128
        // TODO: Add ability to sort with entity fields
129
        if (in_array($field, \samson\activerecord\material::$_attributes)) {
130
            $this->sorter = array(
131
                'field' => $field,
132
                'name' => $field,
133
                'destination' => $destination
134
            );
135
        } else if ($this->isFieldObject($field)) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the usage of else if is discouraged; instead using elseif is recommended.
Loading history...
136
            $this->sorter = array(
137
                'entity' => $field,
138
                'name' => $field->Name,
139
                'field' => in_array($field->Type, array(3, 7, 10)) ? 'numeric_value' : 'value',
140
                'destination' => $destination
141
            );
142
        }
143
    }
144
145
    /**
146
     * Filter collection using navigation entity or collection of them.
147
     * If collection of navigation Url or Ids is passed then this group will be
148
     * applied as single navigation filter to retrieve materials.
149
     *
150
     * @param string|integer|array $navigation Navigation URL or identifier for filtering
151
     * @return self Chaining
152
     */
153
    public function navigation($navigation)
154
    {
155
        // Do not allow empty strings
156
        if (!empty($navigation)) {
157
            // Create id or URL condition
158
            $idOrUrl = new Condition('OR');
159
            $idOrUrl->add('StructureID', $navigation)->add('Url', $navigation);
160
161
            /** @var array $navigationIds  */
162
            $navigationIds = null;
163
            if ($this->query->className('structure')->cond($idOrUrl)->fields('StructureID', $navigationIds)) {
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
164
                // Store all retrieved navigation elements as navigation collection filter
165
                $this->navigation[] = $navigationIds;
166
            }
167
        }
168
169
        // Chaining
170
        return $this;
171
    }
172
173
    /**
174
     * Filter collection using additional field entity.
175
     *
176
     * @param string|integer|\samson\cms\Field $field Additional field identifier or name
177
     * @param mixed $value Additional field value for filtering
178
     * @param string $relation Additional field relation for filtering
179
     * @return self Chaining
180
     */
181
    public function field($field, $value, $relation = dbRelation::EQUAL)
182
    {
183
        // Do not allow empty strings
184
        if ($this->isFieldObject($field)) {
185
            // Get field value column
186
            $valueField = in_array($field->Type, array(3, 7, 10)) ? 'numeric_value' : 'value';
187
			$valueField = $field->Type == 6 ? 'key_value' : $valueField;
0 ignored issues
show
Coding Style introduced by
Spaces must be used to indent lines; tabs are not allowed
Loading history...
188
			
0 ignored issues
show
Coding Style introduced by
Spaces must be used to indent lines; tabs are not allowed
Loading history...
189
            /** @var Condition $condition Ranged condition */
190
            $condition = new Condition('AND');
191
192
            // Add min value for ranged condition
193
            $condition->add($valueField, $value, $relation);
194
195
            // Store retrieved field element and its value as field collection filter
196
            $this->field[] = array($field, $condition);
197
        }
198
199
        // Chaining
200
        return $this;
201
    }
202
203
    /**
204
     * Filter collection using additional field entity values and LIKE relation.
205
     * If this method is called more then once, it will use materials, previously filtered by this method.
206
     *
207
     * @param string $search Search string
208
     * @return self Chaining
209
     */
210
    public function search($search)
211
    {
212
        // If input parameter is a string add it to search string collection
213
        if (isset($search{0})) {
214
            $this->search[] = $search;
215
        }
216
217
        // Chaining
218
        return $this;
219
    }
220
221
    /**
222
     * Filter collection of numeric field in range from min to max values
223
     * @param string|integer|\samson\cms\Field $field Additional field identifier or name
224
     * @param integer $minValue Min value for range filter
225
     * @param integer $maxValue Max value for range filter
226
     * @return self Chaining
227
     */
228
    public function ranged($field, $minValue, $maxValue)
229
    {
230
        // Check input parameters and try to find field
231
        if (($minValue <= $maxValue) && $this->isFieldObject($field)) {
232
            // TODO: Remove integers from code, handle else
233
            // Only numeric fields are supported
234
            if (in_array($field->Type, array(3, 7, 10))) {
235
                /** @var Condition $condition Ranged condition */
236
                $condition = new Condition('AND');
237
238
                // Add min value for ranged condition
239
                $condition->add('numeric_value', $minValue, dbRelation::GREATER_EQ);
240
241
                // Add max value for ranged condition
242
                $condition->add('numeric_value', $maxValue, dbRelation::LOWER_EQ);
243
244
                // Store created condition
245
                $this->field[] = array($field, $condition);
246
            }
247
        }
248
249
        // Chaining
250
        return $this;
251
    }
252
253
    /**
254
     * Try to find additional field record
255
     * @param string|integer $field Additional field identifier or name
256
     * @return bool True if field record has been found
257
     */
258
    protected function isFieldObject(&$field)
259
    {
260
        // Do not allow empty strings
261
        if (!empty($field)) {
262
            // Create id or URL condition
263
            $idOrUrl = new Condition('OR');
264
            $idOrUrl->add('FieldID', $field)->add('Name', $field);
265
266
            // Perform query
267
            return $this->query->className('field')->cond($idOrUrl)->first($field);
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
268
        }
269
270
        // Field not found
271
        return false;
272
    }
273
274
    /**
275
     * Try to get all material identifiers filtered by navigation
276
     * if no navigation filtering is set - nothing will happen.
277
     *
278
     * @param array $filteredIds Collection of filtered material identifiers
279
     * @return bool True if ALL navigation filtering succeeded or there was no filtering at all otherwise false
280
     */
281 View Code Duplication
    protected function applyNavigationFilter(& $filteredIds = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283
        // Iterate all applied navigation filters
284
        foreach ($this->navigation as $navigation) {
285
            // Create navigation-material query
286
            $this->query->className('structurematerial')
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
287
288
                ->cond('StructureID', $navigation)
289
                ->cond('Active', 1)
290
                ->group_by('MaterialID')
291
            ;
292
293
            if (isset($filteredIds)) {
294
                $this->query->cond('MaterialID', $filteredIds);
0 ignored issues
show
Bug introduced by
The method cond() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
295
            }
296
297
            // Perform request to get next portion of filtered material identifiers
298
            if (!$this->query->fields('MaterialID', $filteredIds)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\QueryInterface as the method fields() does only exist in the following implementations of said interface: samsonframework\orm\Query.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
299
                // This filter applying failed
300
                return false;
301
            }
302
        }
303
304
        // We have no navigation collection filters
305
        return true;
306
    }
307
308
    /**
309
     * Try to get all material identifiers filtered by additional field
310
     * if no field filtering is set - nothing will happen.
311
     *
312
     * @param array $filteredIds Collection of filtered material identifiers
313
     * @return bool True if ALL field filtering succeeded or there was no filtering at all otherwise false
314
     */
315 View Code Duplication
    protected function applyFieldFilter(& $filteredIds = array())
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
316
    {
317
        // Iterate all applied field filters
318
        foreach ($this->field as $field) {
319
            // Create material-field query
320
            $this->query->className('materialfield')
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
321
                ->cond('FieldID', $field[0]->id)
322
                ->cond($field[1])
323
                ->group_by('MaterialID')
324
            ;
325
326
            if (isset($filteredIds)) {
327
                $this->query->cond('MaterialID', $filteredIds);
0 ignored issues
show
Bug introduced by
The method cond() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
328
            }
329
330
            // Perform request to get next portion of filtered material identifiers
331
            if (!$this->query->fields('MaterialID', $filteredIds)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\QueryInterface as the method fields() does only exist in the following implementations of said interface: samsonframework\orm\Query.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
332
                // This filter applying failed
333
                return false;
334
            }
335
        }
336
337
        // We have no field collection filters
338
        return true;
339
    }
340
341
    /**
342
     * Try to find all materials which have fields similar to search strings
343
     *
344
     * @param array $filteredIds Collection of filtered material identifiers
345
     * @return bool True if ALL field filtering succeeded or there was no filtering at all otherwise false
346
     */
347
    protected function applySearchFilter(& $filteredIds = array())
348
    {
349
        /** @var array $fields Variable to store all fields related to set navigation */
350
        $fields = array();
351
        /** @var array $navigationArray Array of set navigation identifiers */
352
        $navigationArray = array();
353
        /** @var array $fieldFilter Array of filtered material identifiers via materialfield table */
354
        $fieldFilter = array();
355
        /** @var array $materialFilter Array of filtered material identifiers via material table */
356
        $materialFilter = array();
357
358
        // If there are at least one search string
359
        if (!empty($this->search)) {
360
            // Create array containing all navigation identifiers
361
            foreach ($this->navigation as $navigation) {
362
                $navigationArray = array_merge($navigationArray, $navigation);
363
            }
364
365
            // Get all related fields
366
            $this->query->className('structurefield')
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
367
                ->cond('StructureID', $navigationArray)
368
                ->group_by('FieldID')
369
                ->fields('FieldID', $fields);
370
371
            // Iterate over search strings
372
            foreach ($this->search as $searchString) {
373
                // Try to find search value in materialfield table
374
                $this->query->className('materialfield')
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
375
                    ->cond('FieldID', $fields)
376
                    ->cond('MaterialID', $filteredIds)
377
                    ->cond('Value', '%' . $searchString . '%', dbRelation::LIKE)
378
                    ->cond('Active', 1)
379
                    ->group_by('MaterialID')
380
                    ->fields('MaterialID', $fieldFilter);
381
382
                // TODO: Add generic support for all native fields or their configuration
383
                // Condition to search in material table by Name and URL
384
                $materialCondition = new Condition('OR');
385
                $materialCondition->add('Name', '%' . $searchString . '%', dbRelation::LIKE)
386
                    ->add('Url', '%' . $searchString . '%', dbRelation::LIKE);
387
388
                // Try to find search value in material table
389
                $this->query->className('material')
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
390
                    ->cond('MaterialID', $filteredIds)
391
                    ->cond($materialCondition)
392
                    ->cond('Active', 1)
393
                    ->fields('MaterialID', $materialFilter);
394
395
                // If there are no materials with specified conditions
396
                if (empty($materialFilter) && empty($fieldFilter)) {
397
                    // Filter applying failed
398
                    return false;
399
                } else {// Otherwise set filtered material identifiers
0 ignored issues
show
Coding Style introduced by
Expected 1 newline after opening brace; 0 found
Loading history...
400
                    $filteredIds = array_unique(array_merge($materialFilter, $fieldFilter));
401
                }
402
            }
403
        }
404
405
        // We have no search collection filters
406
        return true;
407
    }
408
409
    /**
410
     * Apply all possible material filters
411
     * @param array $filteredIds Collection of material identifiers
412
     * @return bool True if ALL filtering succeeded or there was no filtering at all otherwise false
413
     */
414
    protected function applyFilter(& $filteredIds = array())
415
    {
416
        return $this->applyNavigationFilter($filteredIds)
417
            && $this->applyFieldFilter($filteredIds)
418
            && $this->applySearchFilter($filteredIds);
419
    }
420
421
    /**
422
     * Perform material identifiers collection sorting
423
     * @param array $materialIDs Variable to return sorted collection
424
     */
425
    protected function applyFieldSorter(& $materialIDs = array())
426
    {
427
        // Check if sorter is configured
428
        if (sizeof($this->sorter)) {
429
            // If we need to sort by entity additional field(column)
430
            if (!in_array($this->sorter['field'], \samson\activerecord\material::$_attributes)) {
431
                // Sort material identifiers by its additional fields
432
                $this->query->className('materialfield')
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
433
                    ->cond('FieldID', $this->sorter['entity']->id)
434
                    ->order_by($this->sorter['field'], $this->sorter['destination'])
435
                    ->cond('MaterialID', $materialIDs)
436
                    ->fields('MaterialID', $materialIDs);
437
            }
438
        }
439
    }
440
441
    /**
442
     * Call handlers stack
443
     * @param array $handlers Collection of callbacks with their parameters
444
     * @param array $params External parameters to pass to callback at first
445
     * @return bool True if all handlers succeeded
446
     */
447
    protected function callHandlers(& $handlers = array(), $params = array())
448
    {
449
        // Call external handlers
450
        foreach ($handlers as $handler) {
451
            // Call external handlers chain
452
            if (call_user_func_array(
453
                $handler[0],
454
                array_merge($params, $handler[1]) // Merge params and handler params
455
            ) === false) {
456
                // Stop - if one of external handlers has failed
457
                return false;
458
            }
459
        }
460
461
        return true;
462
    }
463
464
    /**
465
     * Perform filtering on base material entity
466
     * @param array $materialIDs Variable to return sorted collection
467
     */
468
    protected function applyBaseEntityFilter(& $materialIDs = array())
469
    {
470
        // TODO: Change this to new OOP approach
471
        $class = $this->entityName;
472
473
        // Configure query to base entity
474
        $this->query->className('samson\activerecord\material');
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
475
476
        // Call base material entity handlers to prepare query
477
        $this->callHandlers($this->baseEntityHandlers, array(&$this->query));
478
479
        // Check if sorter is configured
480
        if (sizeof($this->sorter)) {
481
            // If we need to sort by entity own field(column)
482
            if (in_array($this->sorter['field'], $class::$_attributes)) {
483
                // Add material entity sorter
484
                $this->query->order_by($this->sorter['field'], $this->sorter['destination']);
0 ignored issues
show
Bug introduced by
The method order_by() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
485
            }
486
        }
487
488
        // Perform main entity query
489
        $this->materialIDs = $this->query
0 ignored issues
show
Bug introduced by
The method cond() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
490
            ->cond('Active', 1) // Remove deleted entities
491
            ->cond('system', 0) // Remove system entities
492
            ->cond($class::$_primary, $materialIDs) // Filter to current set
493
            ->fields($class::$_primary)
494
        ;
495
    }
496
497
    /**
498
     * Perform collection database retrieval using set filters
499
     *
500
     * @return self Chaining
501
     */
502
    public function fill()
503
    {
504
        // Clear current materials identifiers list
505
        $this->materialIDs = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $materialIDs.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
506
507
        // TODO: Change this to new OOP approach
508
        $class = $this->entityName;
509
510
        // If no filters is set
511
        if (!sizeof($this->search) && !sizeof($this->navigation) && !sizeof($this->field)) {
512
            // Get all entity records identifiers
513
            $this->materialIDs = $this->query->cond('Active', 1)->cond('system', 0)->fields($class::$_primary);
0 ignored issues
show
Bug introduced by
The method cond() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
514
        }
515
516
        // Perform material filtering
517
        if ($this->applyFilter($this->materialIDs)) {
518
            // Now we have all possible material filters applied and final material identifiers collection
519
520
            // Store filtered collection size
521
            $this->count = sizeof($this->materialIDs);
522
523
            // Call material identifier handlers
524
            $this->callHandlers($this->idHandlers, array(&$this->materialIDs));
525
526
            // Perform base entity query for final filtering
527
            $this->applyBaseEntityFilter($this->materialIDs);
528
529
            // Perform sorting
530
            $this->applyFieldSorter($this->materialIDs);
531
532
            // Create count request to count pagination
533
            $this->pager->update(sizeof($this->materialIDs));
534
535
            // Cut only needed materials identifiers from array
536
            $this->materialIDs = array_slice($this->materialIDs, $this->pager->start, $this->pager->end);
0 ignored issues
show
Bug introduced by
Accessing start on the interface samsonframework\pager\PagerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing end on the interface samsonframework\pager\PagerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
537
538
            // Create final material query
539
            $this->query->className($this->entityName)->cond($class::$_primary, $this->materialIDs);
0 ignored issues
show
Bug introduced by
The method className() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
540
541
            // Call material query handlers
542
            $this->callHandlers($this->entityHandlers, array(&$this->query));
543
544
            // Add query sorter for showed page
545
            if (sizeof($this->sorter)) {
546
                $this->query->order_by($this->sorter['name'], $this->sorter['destination']);
0 ignored issues
show
Bug introduced by
The method order_by() does not seem to exist on object<samsonframework\orm\QueryInterface>.

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...
547
            }
548
549
            // Return final filtered entity query result
550
            $this->collection = $this->query->exec();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\QueryInterface as the method exec() does only exist in the following implementations of said interface: samsonframework\orm\Query.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
551
552
        } else { // Collection is empty
0 ignored issues
show
Coding Style introduced by
Expected 1 newline after opening brace; 0 found
Loading history...
553
554
            // Clear current materials identifiers list
555
            $this->materialIDs = array();
556
557
            // Updated pagination
558
            $this->pager->update(sizeof($this->materialIDs));
559
        }
560
561
        // Chaining
562
        return $this;
563
    }
564
}
565