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