PageRenderTime 207ms CodeModel.GetById 121ms app.highlight 17ms RepoModel.GetById 63ms app.codeStats 1ms

/vendor/symfony/finder/Symfony/Component/Finder/Finder.php

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