Completed
Push — develop ( f11ef2...d41b65 )
by David
06:01 queued 11s
created

Finder::sortByModifiedTime()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Finder;
13
14
use Symfony\Component\Finder\Comparator\DateComparator;
15
use Symfony\Component\Finder\Comparator\NumberComparator;
16
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
17
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
18
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
19
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
20
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
21
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
22
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
23
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
24
use Symfony\Component\Finder\Iterator\SortableIterator;
25
26
/**
27
 * Finder allows to build rules to find files and directories.
28
 *
29
 * It is a thin wrapper around several specialized iterator classes.
30
 *
31
 * All rules may be invoked several times.
32
 *
33
 * All methods return the current Finder object to allow chaining:
34
 *
35
 *     $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
36
 *
37
 * @author Fabien Potencier <[email protected]>
38
 */
39
class Finder implements \IteratorAggregate, \Countable
40
{
41
    const IGNORE_VCS_FILES = 1;
42
    const IGNORE_DOT_FILES = 2;
43
    const IGNORE_VCS_IGNORED_FILES = 4;
44
45
    private $mode = 0;
46
    private $names = [];
47
    private $notNames = [];
48
    private $exclude = [];
49
    private $filters = [];
50
    private $depths = [];
51
    private $sizes = [];
52
    private $followLinks = false;
53
    private $reverseSorting = false;
54
    private $sort = false;
55
    private $ignore = 0;
56
    private $dirs = [];
57
    private $dates = [];
58
    private $iterators = [];
59
    private $contains = [];
60
    private $notContains = [];
61
    private $paths = [];
62
    private $notPaths = [];
63
    private $ignoreUnreadableDirs = false;
64
65
    private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
66
67
    public function __construct()
68
    {
69
        $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
70
    }
71
72
    /**
73
     * Creates a new Finder.
74
     *
75
     * @return static
76
     */
77
    public static function create()
78
    {
79
        return new static();
80
    }
81
82
    /**
83
     * Restricts the matching to directories only.
84
     *
85
     * @return $this
86
     */
87
    public function directories()
88
    {
89
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
90
91
        return $this;
92
    }
93
94
    /**
95
     * Restricts the matching to files only.
96
     *
97
     * @return $this
98
     */
99
    public function files()
100
    {
101
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
102
103
        return $this;
104
    }
105
106
    /**
107
     * Adds tests for the directory depth.
108
     *
109
     * Usage:
110
     *
111
     *     $finder->depth('> 1') // the Finder will start matching at level 1.
112
     *     $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
113
     *     $finder->depth(['>= 1', '< 3'])
114
     *
115
     * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
116
     *
117
     * @return $this
118
     *
119
     * @see DepthRangeFilterIterator
120
     * @see NumberComparator
121
     */
122
    public function depth($levels)
123
    {
124
        foreach ((array) $levels as $level) {
125
            $this->depths[] = new Comparator\NumberComparator($level);
126
        }
127
128
        return $this;
129
    }
130
131
    /**
132
     * Adds tests for file dates (last modified).
133
     *
134
     * The date must be something that strtotime() is able to parse:
135
     *
136
     *     $finder->date('since yesterday');
137
     *     $finder->date('until 2 days ago');
138
     *     $finder->date('> now - 2 hours');
139
     *     $finder->date('>= 2005-10-15');
140
     *     $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
141
     *
142
     * @param string|string[] $dates A date range string or an array of date ranges
143
     *
144
     * @return $this
145
     *
146
     * @see strtotime
147
     * @see DateRangeFilterIterator
148
     * @see DateComparator
149
     */
150
    public function date($dates)
151
    {
152
        foreach ((array) $dates as $date) {
153
            $this->dates[] = new Comparator\DateComparator($date);
154
        }
155
156
        return $this;
157
    }
158
159
    /**
160
     * Adds rules that files must match.
161
     *
162
     * You can use patterns (delimited with / sign), globs or simple strings.
163
     *
164
     *     $finder->name('*.php')
165
     *     $finder->name('/\.php$/') // same as above
166
     *     $finder->name('test.php')
167
     *     $finder->name(['test.py', 'test.php'])
168
     *
169
     * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
170
     *
171
     * @return $this
172
     *
173
     * @see FilenameFilterIterator
174
     */
175
    public function name($patterns)
176
    {
177
        $this->names = array_merge($this->names, (array) $patterns);
178
179
        return $this;
180
    }
181
182
    /**
183
     * Adds rules that files must not match.
184
     *
185
     * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
186
     *
187
     * @return $this
188
     *
189
     * @see FilenameFilterIterator
190
     */
191
    public function notName($patterns)
192
    {
193
        $this->notNames = array_merge($this->notNames, (array) $patterns);
194
195
        return $this;
196
    }
197
198
    /**
199
     * Adds tests that file contents must match.
200
     *
201
     * Strings or PCRE patterns can be used:
202
     *
203
     *     $finder->contains('Lorem ipsum')
204
     *     $finder->contains('/Lorem ipsum/i')
205
     *     $finder->contains(['dolor', '/ipsum/i'])
206
     *
207
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
208
     *
209
     * @return $this
210
     *
211
     * @see FilecontentFilterIterator
212
     */
213
    public function contains($patterns)
214
    {
215
        $this->contains = array_merge($this->contains, (array) $patterns);
216
217
        return $this;
218
    }
219
220
    /**
221
     * Adds tests that file contents must not match.
222
     *
223
     * Strings or PCRE patterns can be used:
224
     *
225
     *     $finder->notContains('Lorem ipsum')
226
     *     $finder->notContains('/Lorem ipsum/i')
227
     *     $finder->notContains(['lorem', '/dolor/i'])
228
     *
229
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
230
     *
231
     * @return $this
232
     *
233
     * @see FilecontentFilterIterator
234
     */
235
    public function notContains($patterns)
236
    {
237
        $this->notContains = array_merge($this->notContains, (array) $patterns);
238
239
        return $this;
240
    }
241
242
    /**
243
     * Adds rules that filenames must match.
244
     *
245
     * You can use patterns (delimited with / sign) or simple strings.
246
     *
247
     *     $finder->path('some/special/dir')
248
     *     $finder->path('/some\/special\/dir/') // same as above
249
     *     $finder->path(['some dir', 'another/dir'])
250
     *
251
     * Use only / as dirname separator.
252
     *
253
     * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
254
     *
255
     * @return $this
256
     *
257
     * @see FilenameFilterIterator
258
     */
259
    public function path($patterns)
260
    {
261
        $this->paths = array_merge($this->paths, (array) $patterns);
262
263
        return $this;
264
    }
265
266
    /**
267
     * Adds rules that filenames must not match.
268
     *
269
     * You can use patterns (delimited with / sign) or simple strings.
270
     *
271
     *     $finder->notPath('some/special/dir')
272
     *     $finder->notPath('/some\/special\/dir/') // same as above
273
     *     $finder->notPath(['some/file.txt', 'another/file.log'])
274
     *
275
     * Use only / as dirname separator.
276
     *
277
     * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
278
     *
279
     * @return $this
280
     *
281
     * @see FilenameFilterIterator
282
     */
283
    public function notPath($patterns)
284
    {
285
        $this->notPaths = array_merge($this->notPaths, (array) $patterns);
286
287
        return $this;
288
    }
289
290
    /**
291
     * Adds tests for file sizes.
292
     *
293
     *     $finder->size('> 10K');
294
     *     $finder->size('<= 1Ki');
295
     *     $finder->size(4);
296
     *     $finder->size(['> 10K', '< 20K'])
297
     *
298
     * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
299
     *
300
     * @return $this
301
     *
302
     * @see SizeRangeFilterIterator
303
     * @see NumberComparator
304
     */
305
    public function size($sizes)
306
    {
307
        foreach ((array) $sizes as $size) {
308
            $this->sizes[] = new Comparator\NumberComparator($size);
309
        }
310
311
        return $this;
312
    }
313
314
    /**
315
     * Excludes directories.
316
     *
317
     * Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
318
     *
319
     *     $finder->in(__DIR__)->exclude('ruby');
320
     *
321
     * @param string|array $dirs A directory path or an array of directories
322
     *
323
     * @return $this
324
     *
325
     * @see ExcludeDirectoryFilterIterator
326
     */
327
    public function exclude($dirs)
328
    {
329
        $this->exclude = array_merge($this->exclude, (array) $dirs);
330
331
        return $this;
332
    }
333
334
    /**
335
     * Excludes "hidden" directories and files (starting with a dot).
336
     *
337
     * This option is enabled by default.
338
     *
339
     * @return $this
340
     *
341
     * @see ExcludeDirectoryFilterIterator
342
     */
343
    public function ignoreDotFiles(bool $ignoreDotFiles)
344
    {
345
        if ($ignoreDotFiles) {
346
            $this->ignore |= static::IGNORE_DOT_FILES;
347
        } else {
348
            $this->ignore &= ~static::IGNORE_DOT_FILES;
349
        }
350
351
        return $this;
352
    }
353
354
    /**
355
     * Forces the finder to ignore version control directories.
356
     *
357
     * This option is enabled by default.
358
     *
359
     * @return $this
360
     *
361
     * @see ExcludeDirectoryFilterIterator
362
     */
363
    public function ignoreVCS(bool $ignoreVCS)
364
    {
365
        if ($ignoreVCS) {
366
            $this->ignore |= static::IGNORE_VCS_FILES;
367
        } else {
368
            $this->ignore &= ~static::IGNORE_VCS_FILES;
369
        }
370
371
        return $this;
372
    }
373
374
    /**
375
     * Forces Finder to obey .gitignore and ignore files based on rules listed there.
376
     *
377
     * This option is disabled by default.
378
     *
379
     * @return $this
380
     */
381
    public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
382
    {
383
        if ($ignoreVCSIgnored) {
384
            $this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
385
        } else {
386
            $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
387
        }
388
389
        return $this;
390
    }
391
392
    /**
393
     * Adds VCS patterns.
394
     *
395
     * @see ignoreVCS()
396
     *
397
     * @param string|string[] $pattern VCS patterns to ignore
398
     */
399
    public static function addVCSPattern($pattern)
400
    {
401
        foreach ((array) $pattern as $p) {
402
            self::$vcsPatterns[] = $p;
403
        }
404
405
        self::$vcsPatterns = array_unique(self::$vcsPatterns);
406
    }
407
408
    /**
409
     * Sorts files and directories by an anonymous function.
410
     *
411
     * The anonymous function receives two \SplFileInfo instances to compare.
412
     *
413
     * This can be slow as all the matching files and directories must be retrieved for comparison.
414
     *
415
     * @return $this
416
     *
417
     * @see SortableIterator
418
     */
419
    public function sort(\Closure $closure)
420
    {
421
        $this->sort = $closure;
0 ignored issues
show
Documentation Bug introduced by
It seems like $closure of type object<Closure> is incompatible with the declared type boolean of property $sort.

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...
422
423
        return $this;
424
    }
425
426
    /**
427
     * Sorts files and directories by name.
428
     *
429
     * This can be slow as all the matching files and directories must be retrieved for comparison.
430
     *
431
     * @return $this
432
     *
433
     * @see SortableIterator
434
     */
435
    public function sortByName(bool $useNaturalSort = false)
436
    {
437
        $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME;
438
439
        return $this;
440
    }
441
442
    /**
443
     * Sorts files and directories by type (directories before files), then by name.
444
     *
445
     * This can be slow as all the matching files and directories must be retrieved for comparison.
446
     *
447
     * @return $this
448
     *
449
     * @see SortableIterator
450
     */
451
    public function sortByType()
452
    {
453
        $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
454
455
        return $this;
456
    }
457
458
    /**
459
     * Sorts files and directories by the last accessed time.
460
     *
461
     * This is the time that the file was last accessed, read or written to.
462
     *
463
     * This can be slow as all the matching files and directories must be retrieved for comparison.
464
     *
465
     * @return $this
466
     *
467
     * @see SortableIterator
468
     */
469
    public function sortByAccessedTime()
470
    {
471
        $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
472
473
        return $this;
474
    }
475
476
    /**
477
     * Reverses the sorting.
478
     *
479
     * @return $this
480
     */
481
    public function reverseSorting()
482
    {
483
        $this->reverseSorting = true;
484
485
        return $this;
486
    }
487
488
    /**
489
     * Sorts files and directories by the last inode changed time.
490
     *
491
     * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
492
     *
493
     * On Windows, since inode is not available, changed time is actually the file creation time.
494
     *
495
     * This can be slow as all the matching files and directories must be retrieved for comparison.
496
     *
497
     * @return $this
498
     *
499
     * @see SortableIterator
500
     */
501
    public function sortByChangedTime()
502
    {
503
        $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
504
505
        return $this;
506
    }
507
508
    /**
509
     * Sorts files and directories by the last modified time.
510
     *
511
     * This is the last time the actual contents of the file were last modified.
512
     *
513
     * This can be slow as all the matching files and directories must be retrieved for comparison.
514
     *
515
     * @return $this
516
     *
517
     * @see SortableIterator
518
     */
519
    public function sortByModifiedTime()
520
    {
521
        $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
522
523
        return $this;
524
    }
525
526
    /**
527
     * Filters the iterator with an anonymous function.
528
     *
529
     * The anonymous function receives a \SplFileInfo and must return false
530
     * to remove files.
531
     *
532
     * @return $this
533
     *
534
     * @see CustomFilterIterator
535
     */
536
    public function filter(\Closure $closure)
537
    {
538
        $this->filters[] = $closure;
539
540
        return $this;
541
    }
542
543
    /**
544
     * Forces the following of symlinks.
545
     *
546
     * @return $this
547
     */
548
    public function followLinks()
549
    {
550
        $this->followLinks = true;
551
552
        return $this;
553
    }
554
555
    /**
556
     * Tells finder to ignore unreadable directories.
557
     *
558
     * By default, scanning unreadable directories content throws an AccessDeniedException.
559
     *
560
     * @return $this
561
     */
562
    public function ignoreUnreadableDirs(bool $ignore = true)
563
    {
564
        $this->ignoreUnreadableDirs = $ignore;
565
566
        return $this;
567
    }
568
569
    /**
570
     * Searches files and directories which match defined rules.
571
     *
572
     * @param string|string[] $dirs A directory path or an array of directories
573
     *
574
     * @return $this
575
     *
576
     * @throws DirectoryNotFoundException if one of the directories does not exist
577
     */
578
    public function in($dirs)
579
    {
580
        $resolvedDirs = [];
581
582
        foreach ((array) $dirs as $dir) {
583
            if (is_dir($dir)) {
584
                $resolvedDirs[] = $this->normalizeDir($dir);
585
            } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR | GLOB_NOSORT)) {
586
                sort($glob);
587
                $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob));
588
            } else {
589
                throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
590
            }
591
        }
592
593
        $this->dirs = array_merge($this->dirs, $resolvedDirs);
594
595
        return $this;
596
    }
597
598
    /**
599
     * Returns an Iterator for the current Finder configuration.
600
     *
601
     * This method implements the IteratorAggregate interface.
602
     *
603
     * @return \Iterator|SplFileInfo[] An iterator
604
     *
605
     * @throws \LogicException if the in() method has not been called
606
     */
607
    public function getIterator()
608
    {
609
        if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
610
            throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
611
        }
612
613
        if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
614
            return $this->searchInDirectory($this->dirs[0]);
615
        }
616
617
        $iterator = new \AppendIterator();
618
        foreach ($this->dirs as $dir) {
619
            $iterator->append($this->searchInDirectory($dir));
620
        }
621
622
        foreach ($this->iterators as $it) {
623
            $iterator->append($it);
624
        }
625
626
        return $iterator;
627
    }
628
629
    /**
630
     * Appends an existing set of files/directories to the finder.
631
     *
632
     * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
633
     *
634
     * @return $this
635
     *
636
     * @throws \InvalidArgumentException when the given argument is not iterable
637
     */
638
    public function append(iterable $iterator)
639
    {
640
        if ($iterator instanceof \IteratorAggregate) {
641
            $this->iterators[] = $iterator->getIterator();
642
        } elseif ($iterator instanceof \Iterator) {
643
            $this->iterators[] = $iterator;
644
        } elseif ($iterator instanceof \Traversable || \is_array($iterator)) {
645
            $it = new \ArrayIterator();
646
            foreach ($iterator as $file) {
647
                $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
648
            }
649
            $this->iterators[] = $it;
650
        } else {
651
            throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
652
        }
653
654
        return $this;
655
    }
656
657
    /**
658
     * Check if the any results were found.
659
     *
660
     * @return bool
661
     */
662
    public function hasResults()
663
    {
664
        foreach ($this->getIterator() as $_) {
665
            return true;
666
        }
667
668
        return false;
669
    }
670
671
    /**
672
     * Counts all the results collected by the iterators.
673
     *
674
     * @return int
675
     */
676
    public function count()
677
    {
678
        return iterator_count($this->getIterator());
679
    }
680
681
    private function searchInDirectory(string $dir): \Iterator
682
    {
683
        $exclude = $this->exclude;
684
        $notPaths = $this->notPaths;
685
686
        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
687
            $exclude = array_merge($exclude, self::$vcsPatterns);
688
        }
689
690
        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
691
            $notPaths[] = '#(^|/)\..+(/|$)#';
692
        }
693
694
        if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
695
            $gitignoreFilePath = sprintf('%s/.gitignore', $dir);
696
            if (!is_readable($gitignoreFilePath)) {
697
                throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
698
            }
699
            $notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
700
        }
701
702
        $minDepth = 0;
703
        $maxDepth = PHP_INT_MAX;
704
705
        foreach ($this->depths as $comparator) {
706
            switch ($comparator->getOperator()) {
707
                case '>':
708
                    $minDepth = $comparator->getTarget() + 1;
709
                    break;
710
                case '>=':
711
                    $minDepth = $comparator->getTarget();
712
                    break;
713
                case '<':
714
                    $maxDepth = $comparator->getTarget() - 1;
715
                    break;
716
                case '<=':
717
                    $maxDepth = $comparator->getTarget();
718
                    break;
719
                default:
720
                    $minDepth = $maxDepth = $comparator->getTarget();
721
            }
722
        }
723
724
        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
725
726
        if ($this->followLinks) {
727
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
728
        }
729
730
        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
731
732
        if ($exclude) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $exclude of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
733
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
734
        }
735
736
        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
737
738
        if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) {
739
            $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
740
        }
741
742
        if ($this->mode) {
743
            $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
744
        }
745
746
        if ($this->names || $this->notNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->names of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->notNames of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
747
            $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
748
        }
749
750
        if ($this->contains || $this->notContains) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->contains of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $this->notContains of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
751
            $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
752
        }
753
754
        if ($this->sizes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sizes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
755
            $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
756
        }
757
758
        if ($this->dates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->dates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
759
            $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
760
        }
761
762
        if ($this->filters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->filters of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
763
            $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
764
        }
765
766
        if ($this->paths || $notPaths) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->paths of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
Bug Best Practice introduced by
The expression $notPaths of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
767
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
768
        }
769
770
        if ($this->sort || $this->reverseSorting) {
771
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting);
0 ignored issues
show
Documentation introduced by
$this->sort is of type boolean, but the function expects a integer|callable.

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...
772
            $iterator = $iteratorAggregate->getIterator();
773
        }
774
775
        return $iterator;
776
    }
777
778
    /**
779
     * Normalizes given directory names by removing trailing slashes.
780
     *
781
     * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
782
     */
783
    private function normalizeDir(string $dir): string
784
    {
785
        if ('/' === $dir) {
786
            return $dir;
787
        }
788
789
        $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
790
791
        if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
792
            $dir .= '/';
793
        }
794
795
        return $dir;
796
    }
797
}
798