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