scratch – Blame information for rev 120

Subversion Repositories:
Rev:
Rev Author Line No. Line
120 office 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 /**
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 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 *
114 * @param string|int $level The depth level expression
115 *
116 * @return $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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|int $size A size range string or an integer
287 *
288 * @return $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 $this
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 }