Projekt

Obecné

Profil

Stáhnout (19.7 KB) Statistiky
| Větev: | Revize:
1 cb15593b Cajova-Houba
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
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\Iterator\CustomFilterIterator;
17
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
18
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
19
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
20
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
21
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
22
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
23
use Symfony\Component\Finder\Iterator\SortableIterator;
24
25
/**
26
 * Finder allows to build rules to find files and directories.
27
 *
28
 * It is a thin wrapper around several specialized iterator classes.
29
 *
30
 * All rules may be invoked several times.
31
 *
32
 * All methods return the current Finder object to allow easy chaining:
33
 *
34
 * $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
35
 *
36
 * @author Fabien Potencier <fabien@symfony.com>
37
 */
38
class Finder implements \IteratorAggregate, \Countable
39
{
40
    const IGNORE_VCS_FILES = 1;
41
    const IGNORE_DOT_FILES = 2;
42
43
    private $mode = 0;
44
    private $names = array();
45
    private $notNames = array();
46
    private $exclude = array();
47
    private $filters = array();
48
    private $depths = array();
49
    private $sizes = array();
50
    private $followLinks = false;
51
    private $sort = false;
52
    private $ignore = 0;
53
    private $dirs = array();
54
    private $dates = array();
55
    private $iterators = array();
56
    private $contains = array();
57
    private $notContains = array();
58
    private $paths = array();
59
    private $notPaths = array();
60
    private $ignoreUnreadableDirs = false;
61
62
    private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
63
64
    /**
65
     * Constructor.
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 Finder A new Finder instance
76
     */
77
    public static function create()
78
    {
79
        return new static();
80
    }
81
82
    /**
83
     * Restricts the matching to directories only.
84
     *
85
     * @return Finder|SplFileInfo[] The current Finder instance
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 Finder|SplFileInfo[] The current Finder instance
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
     *
114
     * @param int $level The depth level expression
115
     *
116
     * @return Finder|SplFileInfo[] The current Finder instance
117
     *
118
     * @see DepthRangeFilterIterator
119
     * @see NumberComparator
120
     */
121
    public function depth($level)
122
    {
123
        $this->depths[] = new Comparator\NumberComparator($level);
124
125
        return $this;
126
    }
127
128
    /**
129
     * Adds tests for file dates (last modified).
130
     *
131
     * The date must be something that strtotime() is able to parse:
132
     *
133
     *   $finder->date('since yesterday');
134
     *   $finder->date('until 2 days ago');
135
     *   $finder->date('> now - 2 hours');
136
     *   $finder->date('>= 2005-10-15');
137
     *
138
     * @param string $date A date range string
139
     *
140
     * @return Finder|SplFileInfo[] The current Finder instance
141
     *
142
     * @see strtotime
143
     * @see DateRangeFilterIterator
144
     * @see DateComparator
145
     */
146
    public function date($date)
147
    {
148
        $this->dates[] = new Comparator\DateComparator($date);
149
150
        return $this;
151
    }
152
153
    /**
154
     * Adds rules that files must match.
155
     *
156
     * You can use patterns (delimited with / sign), globs or simple strings.
157
     *
158
     * $finder->name('*.php')
159
     * $finder->name('/\.php$/') // same as above
160
     * $finder->name('test.php')
161
     *
162
     * @param string $pattern A pattern (a regexp, a glob, or a string)
163
     *
164
     * @return Finder|SplFileInfo[] The current Finder instance
165
     *
166
     * @see FilenameFilterIterator
167
     */
168
    public function name($pattern)
169
    {
170
        $this->names[] = $pattern;
171
172
        return $this;
173
    }
174
175
    /**
176
     * Adds rules that files must not match.
177
     *
178
     * @param string $pattern A pattern (a regexp, a glob, or a string)
179
     *
180
     * @return Finder|SplFileInfo[] The current Finder instance
181
     *
182
     * @see FilenameFilterIterator
183
     */
184
    public function notName($pattern)
185
    {
186
        $this->notNames[] = $pattern;
187
188
        return $this;
189
    }
190
191
    /**
192
     * Adds tests that file contents must match.
193
     *
194
     * Strings or PCRE patterns can be used:
195
     *
196
     * $finder->contains('Lorem ipsum')
197
     * $finder->contains('/Lorem ipsum/i')
198
     *
199
     * @param string $pattern A pattern (string or regexp)
200
     *
201
     * @return Finder|SplFileInfo[] The current Finder instance
202
     *
203
     * @see FilecontentFilterIterator
204
     */
205
    public function contains($pattern)
206
    {
207
        $this->contains[] = $pattern;
208
209
        return $this;
210
    }
211
212
    /**
213
     * Adds tests that file contents must not match.
214
     *
215
     * Strings or PCRE patterns can be used:
216
     *
217
     * $finder->notContains('Lorem ipsum')
218
     * $finder->notContains('/Lorem ipsum/i')
219
     *
220
     * @param string $pattern A pattern (string or regexp)
221
     *
222
     * @return Finder|SplFileInfo[] The current Finder instance
223
     *
224
     * @see FilecontentFilterIterator
225
     */
226
    public function notContains($pattern)
227
    {
228
        $this->notContains[] = $pattern;
229
230
        return $this;
231
    }
232
233
    /**
234
     * Adds rules that filenames must match.
235
     *
236
     * You can use patterns (delimited with / sign) or simple strings.
237
     *
238
     * $finder->path('some/special/dir')
239
     * $finder->path('/some\/special\/dir/') // same as above
240
     *
241
     * Use only / as dirname separator.
242
     *
243
     * @param string $pattern A pattern (a regexp or a string)
244
     *
245
     * @return Finder|SplFileInfo[] The current Finder instance
246
     *
247
     * @see FilenameFilterIterator
248
     */
249
    public function path($pattern)
250
    {
251
        $this->paths[] = $pattern;
252
253
        return $this;
254
    }
255
256
    /**
257
     * Adds rules that filenames must not match.
258
     *
259
     * You can use patterns (delimited with / sign) or simple strings.
260
     *
261
     * $finder->notPath('some/special/dir')
262
     * $finder->notPath('/some\/special\/dir/') // same as above
263
     *
264
     * Use only / as dirname separator.
265
     *
266
     * @param string $pattern A pattern (a regexp or a string)
267
     *
268
     * @return Finder|SplFileInfo[] The current Finder instance
269
     *
270
     * @see FilenameFilterIterator
271
     */
272
    public function notPath($pattern)
273
    {
274
        $this->notPaths[] = $pattern;
275
276
        return $this;
277
    }
278
279
    /**
280
     * Adds tests for file sizes.
281
     *
282
     * $finder->size('> 10K');
283
     * $finder->size('<= 1Ki');
284
     * $finder->size(4);
285
     *
286
     * @param string $size A size range string
287
     *
288
     * @return Finder|SplFileInfo[] The current Finder instance
289
     *
290
     * @see SizeRangeFilterIterator
291
     * @see NumberComparator
292
     */
293
    public function size($size)
294
    {
295
        $this->sizes[] = new Comparator\NumberComparator($size);
296
297
        return $this;
298
    }
299
300
    /**
301
     * Excludes directories.
302
     *
303
     * @param string|array $dirs A directory path or an array of directories
304
     *
305
     * @return Finder|SplFileInfo[] The current Finder instance
306
     *
307
     * @see ExcludeDirectoryFilterIterator
308
     */
309
    public function exclude($dirs)
310
    {
311
        $this->exclude = array_merge($this->exclude, (array) $dirs);
312
313
        return $this;
314
    }
315
316
    /**
317
     * Excludes "hidden" directories and files (starting with a dot).
318
     *
319
     * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not
320
     *
321
     * @return Finder|SplFileInfo[] The current Finder instance
322
     *
323
     * @see ExcludeDirectoryFilterIterator
324
     */
325
    public function ignoreDotFiles($ignoreDotFiles)
326
    {
327
        if ($ignoreDotFiles) {
328
            $this->ignore |= static::IGNORE_DOT_FILES;
329
        } else {
330
            $this->ignore &= ~static::IGNORE_DOT_FILES;
331
        }
332
333
        return $this;
334
    }
335
336
    /**
337
     * Forces the finder to ignore version control directories.
338
     *
339
     * @param bool $ignoreVCS Whether to exclude VCS files or not
340
     *
341
     * @return Finder|SplFileInfo[] The current Finder instance
342
     *
343
     * @see ExcludeDirectoryFilterIterator
344
     */
345
    public function ignoreVCS($ignoreVCS)
346
    {
347
        if ($ignoreVCS) {
348
            $this->ignore |= static::IGNORE_VCS_FILES;
349
        } else {
350
            $this->ignore &= ~static::IGNORE_VCS_FILES;
351
        }
352
353
        return $this;
354
    }
355
356
    /**
357
     * Adds VCS patterns.
358
     *
359
     * @see ignoreVCS()
360
     *
361
     * @param string|string[] $pattern VCS patterns to ignore
362
     */
363
    public static function addVCSPattern($pattern)
364
    {
365
        foreach ((array) $pattern as $p) {
366
            self::$vcsPatterns[] = $p;
367
        }
368
369
        self::$vcsPatterns = array_unique(self::$vcsPatterns);
370
    }
371
372
    /**
373
     * Sorts files and directories by an anonymous function.
374
     *
375
     * The anonymous function receives two \SplFileInfo instances to compare.
376
     *
377
     * This can be slow as all the matching files and directories must be retrieved for comparison.
378
     *
379
     * @param \Closure $closure An anonymous function
380
     *
381
     * @return Finder|SplFileInfo[] The current Finder instance
382
     *
383
     * @see SortableIterator
384
     */
385
    public function sort(\Closure $closure)
386
    {
387
        $this->sort = $closure;
388
389
        return $this;
390
    }
391
392
    /**
393
     * Sorts files and directories by name.
394
     *
395
     * This can be slow as all the matching files and directories must be retrieved for comparison.
396
     *
397
     * @return Finder|SplFileInfo[] The current Finder instance
398
     *
399
     * @see SortableIterator
400
     */
401
    public function sortByName()
402
    {
403
        $this->sort = Iterator\SortableIterator::SORT_BY_NAME;
404
405
        return $this;
406
    }
407
408
    /**
409
     * Sorts files and directories by type (directories before files), then by name.
410
     *
411
     * This can be slow as all the matching files and directories must be retrieved for comparison.
412
     *
413
     * @return Finder|SplFileInfo[] The current Finder instance
414
     *
415
     * @see SortableIterator
416
     */
417
    public function sortByType()
418
    {
419
        $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
420
421
        return $this;
422
    }
423
424
    /**
425
     * Sorts files and directories by the last accessed time.
426
     *
427
     * This is the time that the file was last accessed, read or written to.
428
     *
429
     * This can be slow as all the matching files and directories must be retrieved for comparison.
430
     *
431
     * @return Finder|SplFileInfo[] The current Finder instance
432
     *
433
     * @see SortableIterator
434
     */
435
    public function sortByAccessedTime()
436
    {
437
        $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
438
439
        return $this;
440
    }
441
442
    /**
443
     * Sorts files and directories by the last inode changed time.
444
     *
445
     * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
446
     *
447
     * On Windows, since inode is not available, changed time is actually the file creation time.
448
     *
449
     * This can be slow as all the matching files and directories must be retrieved for comparison.
450
     *
451
     * @return Finder|SplFileInfo[] The current Finder instance
452
     *
453
     * @see SortableIterator
454
     */
455
    public function sortByChangedTime()
456
    {
457
        $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
458
459
        return $this;
460
    }
461
462
    /**
463
     * Sorts files and directories by the last modified time.
464
     *
465
     * This is the last time the actual contents of the file were last modified.
466
     *
467
     * This can be slow as all the matching files and directories must be retrieved for comparison.
468
     *
469
     * @return Finder|SplFileInfo[] The current Finder instance
470
     *
471
     * @see SortableIterator
472
     */
473
    public function sortByModifiedTime()
474
    {
475
        $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
476
477
        return $this;
478
    }
479
480
    /**
481
     * Filters the iterator with an anonymous function.
482
     *
483
     * The anonymous function receives a \SplFileInfo and must return false
484
     * to remove files.
485
     *
486
     * @param \Closure $closure An anonymous function
487
     *
488
     * @return Finder|SplFileInfo[] The current Finder instance
489
     *
490
     * @see CustomFilterIterator
491
     */
492
    public function filter(\Closure $closure)
493
    {
494
        $this->filters[] = $closure;
495
496
        return $this;
497
    }
498
499
    /**
500
     * Forces the following of symlinks.
501
     *
502
     * @return Finder|SplFileInfo[] The current Finder instance
503
     */
504
    public function followLinks()
505
    {
506
        $this->followLinks = true;
507
508
        return $this;
509
    }
510
511
    /**
512
     * Tells finder to ignore unreadable directories.
513
     *
514
     * By default, scanning unreadable directories content throws an AccessDeniedException.
515
     *
516
     * @param bool $ignore
517
     *
518
     * @return Finder|SplFileInfo[] The current Finder instance
519
     */
520
    public function ignoreUnreadableDirs($ignore = true)
521
    {
522
        $this->ignoreUnreadableDirs = (bool) $ignore;
523
524
        return $this;
525
    }
526
527
    /**
528
     * Searches files and directories which match defined rules.
529
     *
530
     * @param string|array $dirs A directory path or an array of directories
531
     *
532
     * @return Finder|SplFileInfo[] The current Finder instance
533
     *
534
     * @throws \InvalidArgumentException if one of the directories does not exist
535
     */
536
    public function in($dirs)
537
    {
538
        $resolvedDirs = array();
539
540
        foreach ((array) $dirs as $dir) {
541
            if (is_dir($dir)) {
542
                $resolvedDirs[] = $dir;
543
            } elseif ($glob = glob($dir, (defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) {
544
                $resolvedDirs = array_merge($resolvedDirs, $glob);
545
            } else {
546
                throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir));
547
            }
548
        }
549
550
        $this->dirs = array_merge($this->dirs, $resolvedDirs);
551
552
        return $this;
553
    }
554
555
    /**
556
     * Returns an Iterator for the current Finder configuration.
557
     *
558
     * This method implements the IteratorAggregate interface.
559
     *
560
     * @return \Iterator|SplFileInfo[] An iterator
561
     *
562
     * @throws \LogicException if the in() method has not been called
563
     */
564
    public function getIterator()
565
    {
566
        if (0 === count($this->dirs) && 0 === count($this->iterators)) {
567
            throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
568
        }
569
570
        if (1 === count($this->dirs) && 0 === count($this->iterators)) {
571
            return $this->searchInDirectory($this->dirs[0]);
572
        }
573
574
        $iterator = new \AppendIterator();
575
        foreach ($this->dirs as $dir) {
576
            $iterator->append($this->searchInDirectory($dir));
577
        }
578
579
        foreach ($this->iterators as $it) {
580
            $iterator->append($it);
581
        }
582
583
        return $iterator;
584
    }
585
586
    /**
587
     * Appends an existing set of files/directories to the finder.
588
     *
589
     * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
590
     *
591
     * @param mixed $iterator
592
     *
593
     * @return Finder|SplFileInfo[] The finder
594
     *
595
     * @throws \InvalidArgumentException When the given argument is not iterable.
596
     */
597
    public function append($iterator)
598
    {
599
        if ($iterator instanceof \IteratorAggregate) {
600
            $this->iterators[] = $iterator->getIterator();
601
        } elseif ($iterator instanceof \Iterator) {
602
            $this->iterators[] = $iterator;
603
        } elseif ($iterator instanceof \Traversable || is_array($iterator)) {
604
            $it = new \ArrayIterator();
605
            foreach ($iterator as $file) {
606
                $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
607
            }
608
            $this->iterators[] = $it;
609
        } else {
610
            throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
611
        }
612
613
        return $this;
614
    }
615
616
    /**
617
     * Counts all the results collected by the iterators.
618
     *
619
     * @return int
620
     */
621
    public function count()
622
    {
623
        return iterator_count($this->getIterator());
624
    }
625
626
    /**
627
     * @param $dir
628
     *
629
     * @return \Iterator
630
     */
631
    private function searchInDirectory($dir)
632
    {
633
        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
634
            $this->exclude = array_merge($this->exclude, self::$vcsPatterns);
635
        }
636
637
        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
638
            $this->notPaths[] = '#(^|/)\..+(/|$)#';
639
        }
640
641
        $minDepth = 0;
642
        $maxDepth = PHP_INT_MAX;
643
644
        foreach ($this->depths as $comparator) {
645
            switch ($comparator->getOperator()) {
646
                case '>':
647
                    $minDepth = $comparator->getTarget() + 1;
648
                    break;
649
                case '>=':
650
                    $minDepth = $comparator->getTarget();
651
                    break;
652
                case '<':
653
                    $maxDepth = $comparator->getTarget() - 1;
654
                    break;
655
                case '<=':
656
                    $maxDepth = $comparator->getTarget();
657
                    break;
658
                default:
659
                    $minDepth = $maxDepth = $comparator->getTarget();
660
            }
661
        }
662
663
        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
664
665
        if ($this->followLinks) {
666
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
667
        }
668
669
        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
670
671
        if ($this->exclude) {
672
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
673
        }
674
675
        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
676
677
        if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) {
678
            $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
679
        }
680
681
        if ($this->mode) {
682
            $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
683
        }
684
685
        if ($this->names || $this->notNames) {
686
            $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
687
        }
688
689
        if ($this->contains || $this->notContains) {
690
            $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
691
        }
692
693
        if ($this->sizes) {
694
            $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
695
        }
696
697
        if ($this->dates) {
698
            $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
699
        }
700
701
        if ($this->filters) {
702
            $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
703
        }
704
705
        if ($this->paths || $this->notPaths) {
706
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
707
        }
708
709
        if ($this->sort) {
710
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
711
            $iterator = $iteratorAggregate->getIterator();
712
        }
713
714
        return $iterator;
715
    }
716
}