Completed
Pull Request — master (#53)
by
unknown
02:32
created

Collection::applyFieldSorter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 3
eloc 8
nc 3
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\api;
9
10
use samsonframework\orm\Condition;
11
use samsonframework\collection\Paged;
12
use samsonframework\orm\Relation;
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
 * @deprecated Use generated Entities and EntityQueries classes.
22
 */
23
class Collection extends Paged
24
{
25
    /** @var string Entity manager instance */
26
    protected $managerEntity = '\samsoncms\api\query\Generic';
27
28
    /** @var array Collection for current filtered material identifiers */
29
    protected $materialIDs = array();
30
31
    /** @var array Collection of navigation filters */
32
    protected $navigation = array();
33
34
    /** @var array Collection of field filters */
35
    protected $field = array();
36
37
    /** @var array Collection of query handlers */
38
    protected $idHandlers = array();
39
40
    /** @var array External material handler and params array */
41
    protected $entityHandlers = array();
42
43
    /** @var array Base material entity handler callbacks array */
44
    protected $baseEntityHandlers = array();
45
46
    /** @var string Collection entities class name */
47
    protected $entityName = 'samson\cms\CMSMaterial';
48
49
    /**
50
     * Generic collection constructor
51
     *
52
     * @param RenderInterface $renderer View render object
53
     * @param QueryInterface  $query    Query object
54
     */
55
    public function __construct(RenderInterface $renderer, QueryInterface $query, PagerInterface $pager)
56
    {
57
        // Call parent initialization
58
        parent::__construct($renderer, $query->entity('\samson\activerecord\material'), $pager);
59
    }
60
61
    /**
62
     * Render products collection block
63
     *
64
     * @param string $prefix     Prefix for view variables
65
     * @param array  $restricted Collection of ignored keys
66
     *
67
     * @return array Collection key => value
68
     */
69
    public function toView($prefix = null, array $restricted = array())
70
    {
71
        // Render pager and collection
72
        return array_diff(array(
73
            $prefix . 'html' => $this->render(),
74
            $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...
Bug introduced by
The method toHTML() does not seem to exist on object<samsonframework\pager\PagerInterface>.

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...
75
        ), $restricted);
76
    }
77
78
    /**
79
     * Add external identifier filter handler
80
     *
81
     * @param callback $handler
82
     * @param array    $params
83
     *
84
     * @return $this Chaining
85
     */
86
    public function handler($handler, array $params = array())
87
    {
88
        // Add callback with parameters to array
89
        $this->idHandlers[] = array($handler, $params);
90
91
        return $this;
92
    }
93
94
    /**
95
     * Set external entity handler
96
     *
97
     * @param callback $handler
98
     * @param array    $params
99
     *
100
     * @return $this Chaining
101
     */
102
    public function baseEntityHandler($handler, array $params = array())
103
    {
104
        // Add callback with parameters to array
105
        $this->baseEntityHandlers[] = array($handler, $params);
106
107
        return $this;
108
    }
109
110
    /**
111
     * Set external entity handler
112
     *
113
     * @param callback $handler
114
     * @param array    $params
115
     *
116
     * @return $this Chaining
117
     */
118
    public function entityHandler($handler, array $params = array())
119
    {
120
        // Add callback with parameters to array
121
        $this->entityHandlers[] = array($handler, $params);
122
123
        return $this;
124
    }
125
126
    /**
127
     * Set collection sorter parameters
128
     *
129
     * @param string|integer $field       Field identifier or name
130
     * @param string         $destination ASC|DESC
131
     *
132
     * @return void
133
     */
134
    public function sorter($field, $destination = 'ASC')
135
    {
136
        /**@var \samson\activerecord\field $field */
137
        // TODO: Add ability to sort with entity fields
138
        if (in_array($field, \samson\activerecord\material::$_attributes)) {
139
            $this->sorter = array(
140
                'field' => $field,
141
                'name' => $field,
142
                'destination' => $destination
143
            );
144
        } elseif ($this->isFieldObject($field)) {
145
            $this->sorter = array(
146
                'entity' => $field,
147
                'name' => $field->Name,
148
                'field' => in_array($field->Type, array(3, 7, 10)) ? 'numeric_value' : 'value',
149
                'destination' => $destination
150
            );
151
        }
152
    }
153
154
    /**
155
     * Filter collection using navigation entity or collection of them.
156
     * If collection of navigation Url or Ids is passed then this group will be
157
     * applied as single navigation filter to retrieve materials.
158
     *
159
     * @param string|integer|array $navigation Navigation URL or identifier for filtering
160
     *
161
     * @return $this Chaining
162
     */
163
    public function navigation($navigation)
164
    {
165
        // Do not allow empty strings
166
        if (!empty($navigation)) {
167
            // Create id or URL condition
168
            $idOrUrl = new Condition('OR');
169
            $idOrUrl->add('StructureID', $navigation)->add('Url', $navigation);
170
171
            /** @var array $navigationIds */
172
            $navigationIds = null;
173
            if ($this->query->entity('\samson\activerecord\structure')->whereCondition($idOrUrl)->fields('StructureID', $navigationIds)) {
174
                // Store all retrieved navigation elements as navigation collection filter
175
                $this->navigation[] = $navigationIds;
176
            }
177
        }
178
179
        // Chaining
180
        return $this;
181
    }
182
183
    /**
184
     * Filter collection using additional field entity.
185
     *
186
     * @param string|integer|Field $field    Additional field identifier or name
187
     * @param mixed                $value    Additional field value for filtering
188
     * @param string               $relation Additional field relation for filtering
189
     *
190
     * @return $this Chaining
191
     */
192
    public function field($field, $value, $relation = Relation::EQUAL)
193
    {
194
        // Do not allow empty strings
195
        if ($this->isFieldObject($field)) {
196
            // Get field value column
197
            $valueField = in_array($field->Type, array(3, 7, 10)) ? 'numeric_value' : 'value';
198
            $valueField = $field->Type == 6 ? 'key_value' : $valueField;
199
200
            /** @var Condition $condition Ranged condition */
201
            $condition = new Condition('AND');
202
203
            // Add min value for ranged condition
204
            $condition->add($valueField, $value, $relation);
205
206
            // Store retrieved field element and its value as field collection filter
207
            $this->field[] = array($field, $condition);
208
        }
209
210
        // Chaining
211
        return $this;
212
    }
213
214
    /**
215
     * Filter collection using additional field entity values and LIKE relation.
216
     * If this method is called more then once, it will use materials, previously filtered by this method.
217
     *
218
     * @param string $search Search string
219
     *
220
     * @return $this Chaining
221
     */
222
    public function search($search)
223
    {
224
        // If input parameter is a string add it to search string collection
225
        if (isset($search{0})) {
226
            $this->search[] = $search;
227
        }
228
229
        // Chaining
230
        return $this;
231
    }
232
233
    /**
234
     * Filter collection of numeric field in range from min to max values
235
     *
236
     * @param string|integer|Field $field    Additional field identifier or name
237
     * @param integer              $minValue Min value for range filter
238
     * @param integer              $maxValue Max value for range filter
239
     *
240
     * @return $this Chaining
241
     */
242
    public function ranged($field, $minValue, $maxValue)
243
    {
244
        // Check input parameters and try to find field
245
        if (($minValue <= $maxValue) && $this->isFieldObject($field)) {
246
            // TODO: Remove integers from code, handle else
247
            // Only numeric fields are supported
248
            if (in_array($field->Type, array(3, 7, 10))) {
249
                /** @var Condition $condition Ranged condition */
250
                $condition = new Condition('AND');
251
252
                // Add min value for ranged condition
253
                $condition->add('numeric_value', $minValue, Relation::GREATER_EQ);
254
255
                // Add max value for ranged condition
256
                $condition->add('numeric_value', $maxValue, Relation::LOWER_EQ);
257
258
                // Store created condition
259
                $this->field[] = array($field, $condition);
260
            }
261
        }
262
263
        // Chaining
264
        return $this;
265
    }
266
267
    /**
268
     * Try to find additional field record
269
     *
270
     * @param string|integer $field Additional field identifier or name
271
     *
272
     * @return bool True if field record has been found
273
     */
274
    protected function isFieldObject(&$field)
275
    {
276
        // Do not allow empty strings
277
        if (!empty($field)) {
278
            // Create id or URL condition
279
            $idOrUrl = new Condition('OR');
280
            $idOrUrl->add('FieldID', $field)->add('Name', $field);
281
282
            // Perform query
283
            return $this->query->entity('\samson\activerecord\field')->whereCondition($idOrUrl)->first($field);
0 ignored issues
show
Documentation introduced by
$field is of type string|integer, but the function expects a null|object<samsonframework\orm\RecordInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
284
        }
285
286
        // Field not found
287
        return false;
288
    }
289
290
    /**
291
     * Try to get all material identifiers filtered by navigation
292
     * if no navigation filtering is set - nothing will happen.
293
     *
294
     * @param array $filteredIds Collection of filtered material identifiers
295
     *
296
     * @return bool True if ALL navigation filtering succeeded or there was no filtering at all otherwise false
297
     */
298 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...
299
    {
300
        // Iterate all applied navigation filters
301
        foreach ($this->navigation as $navigation) {
302
            // Create navigation-material query
303
            $this->query->entity('\samson\activerecord\structurematerial')
304
                ->where('StructureID', $navigation)
305
                ->where('Active', 1)
306
                ->groupBy('MaterialID');
307
308
            if (null !== $filteredIds) {
309
                $this->query->where('MaterialID', $filteredIds);
310
            }
311
312
            // Perform request to get next portion of filtered material identifiers
313
            if (!$this->query->fields('MaterialID', $filteredIds)) {
0 ignored issues
show
Bug introduced by
It seems like $filteredIds defined by parameter $filteredIds on line 298 can also be of type array; however, samsonframework\orm\QueryInterface::fields() does only seem to accept null|array<integer,objec...k\orm\RecordInterface>>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
314
                // This filter applying failed
315
                return false;
316
            }
317
        }
318
319
        // We have no navigation collection filters
320
        return true;
321
    }
322
323
    /**
324
     * Try to get all material identifiers filtered by additional field
325
     * if no field filtering is set - nothing will happen.
326
     *
327
     * @param array $filteredIds Collection of filtered material identifiers
328
     *
329
     * @return bool True if ALL field filtering succeeded or there was no filtering at all otherwise false
330
     */
331 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...
332
    {
333
        // Iterate all applied field filters
334
        foreach ($this->field as $field) {
335
            // Create material-field query
336
            $this->query->entity('\samson\activerecord\materialfield')
337
                ->where('FieldID', $field[0]->id)
338
                ->whereCondition($field[1])
339
                ->groupBy('MaterialID');
340
341
            if (null !== $filteredIds) {
342
                $this->query->where('MaterialID', $filteredIds);
343
            }
344
345
            // Perform request to get next portion of filtered material identifiers
346
            if (!$this->query->fields('MaterialID', $filteredIds)) {
0 ignored issues
show
Bug introduced by
It seems like $filteredIds defined by parameter $filteredIds on line 331 can also be of type array; however, samsonframework\orm\QueryInterface::fields() does only seem to accept null|array<integer,objec...k\orm\RecordInterface>>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
347
                // This filter applying failed
348
                return false;
349
            }
350
        }
351
352
        // We have no field collection filters
353
        return true;
354
    }
355
356
    /**
357
     * Try to find all materials which have fields similar to search strings
358
     *
359
     * @param array $filteredIds Collection of filtered material identifiers
360
     *
361
     * @return bool True if ALL field filtering succeeded or there was no filtering at all otherwise false
362
     */
363
    protected function applySearchFilter(&$filteredIds = array())
364
    {
365
        /** @var array $fields Variable to store all fields related to set navigation */
366
        $fields = array();
367
        /** @var array $navigationArray Array of set navigation identifiers */
368
        $navigationArray = array();
369
        /** @var array $fieldFilter Array of filtered material identifiers via materialfield table */
370
        $fieldFilter = array();
371
        /** @var array $materialFilter Array of filtered material identifiers via material table */
372
        $materialFilter = array();
0 ignored issues
show
Unused Code introduced by
$materialFilter is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
373
374
        // If there are at least one search string
375
        if (!empty($this->search)) {
376
            // Create array containing all navigation identifiers
377
            foreach ($this->navigation as $navigation) {
378
                // Navigation hook for searching action
379
                $navigation = is_array($navigation) ? $navigation : array($navigation);
380
                $navigationArray = array_merge($navigationArray, $navigation);
381
            }
382
383
            // Get all related fields
384
            $this->query->entity('\samson\activerecord\structurefield')
385
                ->where('StructureID', $navigationArray)
386
                ->groupBy('FieldID')
387
                ->fields('FieldID', $fields);
388
389
            // Iterate over search strings
390
            foreach ($this->search as $searchString) {
391
                // Try to find search value in materialfield table
392
                $this->query->entity('\samson\activerecord\materialfield')
393
                    ->where('FieldID', $fields)
394
                    ->where('MaterialID', $filteredIds)
395
                    ->where('Value', '%' . $searchString . '%', Relation::LIKE)
396
                    ->where('Active', 1)
397
                    ->groupBy('MaterialID')
398
                    ->fields('MaterialID', $fieldFilter);
0 ignored issues
show
Bug introduced by
It seems like $fieldFilter defined by array() on line 370 can also be of type array; however, samsonframework\orm\QueryInterface::fields() does only seem to accept null|array<integer,objec...k\orm\RecordInterface>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
399
400
                // TODO: Add generic support for all native fields or their configuration
401
                // Condition to search in material table by Name and URL
402
                $materialCondition = new Condition('OR');
403
                $materialCondition->add('Name', '%' . $searchString . '%', Relation::LIKE)
404
                    ->add('Url', '%' . $searchString . '%', Relation::LIKE);
405
406
407
                // Try to find search value in material table
408
                $this->query->entity('\samson\activerecord\material')
409
                    ->whereCondition($materialCondition)
410
                    ->where('Active', 1);
411
412
                // If we have not empty collection of filtering identifiers
413
                if (count($filteredIds)) {
414
                    $this->query->where('MaterialID', $filteredIds);
415
                }
416
417
                $materialFilter = $this->query->fields('MaterialID');
418
419
                // If there are no materials with specified conditions
420
                if (empty($materialFilter) && empty($fieldFilter) && count($materialFilter) != 0 && count($fieldFilter != 0)) {
421
                    // Filter applying failed
422
                    return false;
423
                } else {// Otherwise set filtered material identifiers
424
                    $filteredIds = array_unique(array_merge($materialFilter, $fieldFilter));
425
                }
426
            }
427
        }
428
429
        // We have no search collection filters
430
        return true;
431
    }
432
433
    /**
434
     * Apply all possible material filters
435
     *
436
     * @param array $filteredIds Collection of material identifiers
437
     *
438
     * @return bool True if ALL filtering succeeded or there was no filtering at all otherwise false
439
     */
440
    protected function applyFilter(& $filteredIds = array())
441
    {
442
        return $this->applyNavigationFilter($filteredIds)
443
        && $this->applyFieldFilter($filteredIds)
444
        && $this->applySearchFilter($filteredIds)
445
        && $this->applyMaterialSorter($filteredIds);
446
    }
447
448
    /**
449
     * Perform material identifiers collection sorting
450
     *
451
     * @param array $materialIDs Variable to return sorted collection
452
     */
453
    protected function applyFieldSorter(&$materialIDs = array())
454
    {
455
        // Check if sorter is configured
456
        if (count($this->sorter)) {
457
            // If we need to sort by entity additional field(column)
458
            if (!in_array($this->sorter['field'], \samson\activerecord\material::$_attributes)) {
459
                // Sort material identifiers by its additional fields
460
                $this->query->entity('\samson\activerecord\materialfield')
461
                    ->where('FieldID', $this->sorter['entity']->id)
462
                    ->orderBy($this->sorter['field'], $this->sorter['destination'])
463
                    ->where('MaterialID', $materialIDs)
464
                    ->fields('MaterialID', $materialIDs);
465
            }
466
        }
467
    }
468
469
    /**
470
     * Perform material own fields sorting
471
     *
472
     * @param array $materialIDs Variable to return sorted collection
473
     *
474
     * @return bool Always true as we are just sorting
475
     */
476
    protected function applyMaterialSorter(&$materialIDs = array())
477
    {
478
        // Check if sorter is configured
479
        if (count($this->sorter)) {
480
            // If we need to sort by entity additional field(column)
481
            if (in_array($this->sorter['field'], \samson\activerecord\material::$_attributes)) {
482
                // Sort material identifiers by its additional fields
483
                $this->query->entity('\samson\activerecord\material')
484
                    ->orderBy($this->sorter['field'], $this->sorter['destination'])
485
                    ->fields('MaterialID', $materialIDs);
486
            }
487
        }
488
489
        return true;
490
    }
491
492
    /**
493
     * Call handlers stack
494
     *
495
     * @param array $handlers Collection of callbacks with their parameters
496
     * @param array $params   External parameters to pass to callback at first
497
     *
498
     * @return bool True if all handlers succeeded
499
     */
500
    protected function callHandlers(&$handlers = array(), $params = array())
501
    {
502
        // Call external handlers
503
        foreach ($handlers as $handler) {
504
            // Call external handlers chain
505
            if (call_user_func_array($handler[0], array_merge($params, $handler[1])) === false) {
506
                // Stop - if one of external handlers has failed
507
                return false;
508
            }
509
        }
510
511
        return true;
512
    }
513
514
    /**
515
     * Perform filtering on base material entity
516
     *
517
     * @param array $materialIDs Variable to return sorted collection
518
     */
519
    protected function applyBaseEntityFilter(&$materialIDs = array())
520
    {
521
        // TODO: Change this to new OOP approach
522
        $class = $this->entityName;
523
524
        // Configure query to base entity
525
        $this->query->entity('samson\activerecord\material');
526
527
        // Call base material entity handlers to prepare query
528
        $this->callHandlers($this->baseEntityHandlers, array(&$this->query));
529
530
        // Check if sorter is configured
531 View Code Duplication
        if (count($this->sorter)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
532
            // If we need to sort by entity own field(column)
533
            if (in_array($this->sorter['field'], $class::$_attributes)) {
534
                // Add material entity sorter
535
                $this->query->orderBy($this->sorter['field'], $this->sorter['destination']);
536
            }
537
        }
538
539
        // Perform main entity query
540
        $this->materialIDs = $this->query
541
            ->where('Active', 1)// Remove deleted entities
542
            ->where('system', 0)// Remove system entities
543
            ->where($class::$_primary, $materialIDs)// Filter to current set
544
            ->fields($class::$_primary);
545
    }
546
547
    /**
548
     * Perform collection database retrieval using set filters
549
     *
550
     * @return $this Chaining
551
     */
552
    public function fill()
553
    {
554
        // Clear current materials identifiers list
555
        $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...
556
557
        // TODO: Change this to new OOP approach
558
        $class = $this->entityName;
559
560
        // If no filters is set
561
        if (!count($this->search) && !count($this->navigation) && !count($this->field)) {
562
            // Add sorting if present for material table
563 View Code Duplication
            if (count($this->sorter) && !array_key_exists('enitity', $this->sorter)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
564
                $this->query->orderBy($this->sorter['field'], $this->sorter['destination']);
565
            }
566
            // Get all entity records identifiers
567
            $this->materialIDs = $this->query->where('Active', 1)->where('system', 0)->fields($class::$_primary);
568
        }
569
570
        // Perform material filtering
571
        if ($this->applyFilter($this->materialIDs)) {
0 ignored issues
show
Bug introduced by
It seems like $this->materialIDs can also be of type null; however, samsoncms\api\Collection::applyFilter() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
572
            // Now we have all possible material filters applied and final material identifiers collection
573
574
            // Store filtered collection size
575
            $this->count = count($this->materialIDs);
576
577
            // Call material identifier handlers
578
            $this->callHandlers($this->idHandlers, array(&$this->materialIDs));
579
580
            // Perform base entity query for final filtering
581
            $this->applyBaseEntityFilter($this->materialIDs);
582
583
            // Perform sorting
584
            $this->applyFieldSorter($this->materialIDs);
585
586
            // Create count request to count pagination
587
            $this->pager->update(count($this->materialIDs));
0 ignored issues
show
Bug introduced by
The method update() does not seem to exist on object<samsonframework\pager\PagerInterface>.

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...
588
589
            // Cut only needed materials identifiers from array
590
            $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...
591
592
            // Create final material query
593
            $this->query->entity($this->entityName)->where($class::$_primary, $this->materialIDs);
594
595
            // Call material query handlers
596
            $this->callHandlers($this->entityHandlers, array(&$this->query));
597
598
            // Add query sorter for showed page
599
            if (count($this->sorter)) {
600
                $this->query->orderBy($this->sorter['name'], $this->sorter['destination']);
601
            }
602
603
            // Return final filtered entity query result
604
            $this->collection = $this->query->exec();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->query->exec() can also be of type boolean. However, the property $collection is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
605
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
606
        } else { // Collection is empty
607
608
            // Clear current materials identifiers list
609
            $this->materialIDs = array();
610
611
            // Updated pagination
612
            $this->pager->update(count($this->materialIDs));
0 ignored issues
show
Bug introduced by
The method update() does not seem to exist on object<samsonframework\pager\PagerInterface>.

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...
613
        }
614
615
        // Chaining
616
        return $this;
617
    }
618
}
619