Mercurial > hg > cmmr2012-drupal-site
comparison vendor/symfony/finder/Finder.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
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 } |