vendor/symfony/src/Symfony/Component/Finder/Finder.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     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 /**
       
    15  * Finder allows to build rules to find files and directories.
       
    16  *
       
    17  * It is a thin wrapper around several specialized iterator classes.
       
    18  *
       
    19  * All rules may be invoked several times.
       
    20  *
       
    21  * All methods return the current Finder object to allow easy chaining:
       
    22  *
       
    23  * $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
       
    24  *
       
    25  * @author Fabien Potencier <fabien@symfony.com>
       
    26  *
       
    27  * @api
       
    28  */
       
    29 class Finder implements \IteratorAggregate
       
    30 {
       
    31     const IGNORE_VCS_FILES = 1;
       
    32     const IGNORE_DOT_FILES = 2;
       
    33 
       
    34     private $mode        = 0;
       
    35     private $names       = array();
       
    36     private $notNames    = array();
       
    37     private $exclude     = array();
       
    38     private $filters     = array();
       
    39     private $depths      = array();
       
    40     private $sizes       = array();
       
    41     private $followLinks = false;
       
    42     private $sort        = false;
       
    43     private $ignore      = 0;
       
    44     private $dirs        = array();
       
    45     private $dates       = array();
       
    46     private $iterators   = array();
       
    47 
       
    48     static private $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
       
    49 
       
    50     /**
       
    51      * Constructor.
       
    52      */
       
    53     public function __construct()
       
    54     {
       
    55         $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
       
    56     }
       
    57 
       
    58     /**
       
    59      * Creates a new Finder.
       
    60      *
       
    61      * @return Finder A new Finder instance
       
    62      *
       
    63      * @api
       
    64      */
       
    65     static public function create()
       
    66     {
       
    67         return new self();
       
    68     }
       
    69 
       
    70     /**
       
    71      * Restricts the matching to directories only.
       
    72      *
       
    73      * @return Finder The current Finder instance
       
    74      *
       
    75      * @api
       
    76      */
       
    77     public function directories()
       
    78     {
       
    79         $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
       
    80 
       
    81         return $this;
       
    82     }
       
    83 
       
    84     /**
       
    85      * Restricts the matching to files only.
       
    86      *
       
    87      * @return Finder The current Finder instance
       
    88      *
       
    89      * @api
       
    90      */
       
    91     public function files()
       
    92     {
       
    93         $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
       
    94 
       
    95         return $this;
       
    96     }
       
    97 
       
    98     /**
       
    99      * Adds tests for the directory depth.
       
   100      *
       
   101      * Usage:
       
   102      *
       
   103      *   $finder->depth('> 1') // the Finder will start matching at level 1.
       
   104      *   $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
       
   105      *
       
   106      * @param  int $level The depth level expression
       
   107      *
       
   108      * @return Finder The current Finder instance
       
   109      *
       
   110      * @see Symfony\Component\Finder\Iterator\DepthRangeFilterIterator
       
   111      * @see Symfony\Component\Finder\Comparator\NumberComparator
       
   112      *
       
   113      * @api
       
   114      */
       
   115     public function depth($level)
       
   116     {
       
   117         $this->depths[] = new Comparator\NumberComparator($level);
       
   118 
       
   119         return $this;
       
   120     }
       
   121 
       
   122     /**
       
   123      * Adds tests for file dates (last modified).
       
   124      *
       
   125      * The date must be something that strtotime() is able to parse:
       
   126      *
       
   127      *   $finder->date('since yesterday');
       
   128      *   $finder->date('until 2 days ago');
       
   129      *   $finder->date('> now - 2 hours');
       
   130      *   $finder->date('>= 2005-10-15');
       
   131      *
       
   132      * @param  string $date A date rage string
       
   133      *
       
   134      * @return Finder The current Finder instance
       
   135      *
       
   136      * @see strtotime
       
   137      * @see Symfony\Component\Finder\Iterator\DateRangeFilterIterator
       
   138      * @see Symfony\Component\Finder\Comparator\DateComparator
       
   139      *
       
   140      * @api
       
   141      */
       
   142     public function date($date)
       
   143     {
       
   144         $this->dates[] = new Comparator\DateComparator($date);
       
   145 
       
   146         return $this;
       
   147     }
       
   148 
       
   149     /**
       
   150      * Adds rules that files must match.
       
   151      *
       
   152      * You can use patterns (delimited with / sign), globs or simple strings.
       
   153      *
       
   154      * $finder->name('*.php')
       
   155      * $finder->name('/\.php$/') // same as above
       
   156      * $finder->name('test.php')
       
   157      *
       
   158      * @param  string $pattern A pattern (a regexp, a glob, or a string)
       
   159      *
       
   160      * @return Finder The current Finder instance
       
   161      *
       
   162      * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
       
   163      *
       
   164      * @api
       
   165      */
       
   166     public function name($pattern)
       
   167     {
       
   168         $this->names[] = $pattern;
       
   169 
       
   170         return $this;
       
   171     }
       
   172 
       
   173     /**
       
   174      * Adds rules that files must not match.
       
   175      *
       
   176      * @param  string $pattern A pattern (a regexp, a glob, or a string)
       
   177      *
       
   178      * @return Finder The current Finder instance
       
   179      *
       
   180      * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
       
   181      *
       
   182      * @api
       
   183      */
       
   184     public function notName($pattern)
       
   185     {
       
   186         $this->notNames[] = $pattern;
       
   187 
       
   188         return $this;
       
   189     }
       
   190 
       
   191     /**
       
   192      * Adds tests for file sizes.
       
   193      *
       
   194      * $finder->size('> 10K');
       
   195      * $finder->size('<= 1Ki');
       
   196      * $finder->size(4);
       
   197      *
       
   198      * @param string $size A size range string
       
   199      *
       
   200      * @return Finder The current Finder instance
       
   201      *
       
   202      * @see Symfony\Component\Finder\Iterator\SizeRangeFilterIterator
       
   203      * @see Symfony\Component\Finder\Comparator\NumberComparator
       
   204      *
       
   205      * @api
       
   206      */
       
   207     public function size($size)
       
   208     {
       
   209         $this->sizes[] = new Comparator\NumberComparator($size);
       
   210 
       
   211         return $this;
       
   212     }
       
   213 
       
   214     /**
       
   215      * Excludes directories.
       
   216      *
       
   217      * @param  string $dir A directory to exclude
       
   218      *
       
   219      * @return Finder The current Finder instance
       
   220      *
       
   221      * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
       
   222      *
       
   223      * @api
       
   224      */
       
   225     public function exclude($dir)
       
   226     {
       
   227         $this->exclude[] = $dir;
       
   228 
       
   229         return $this;
       
   230     }
       
   231 
       
   232     /**
       
   233      * Excludes "hidden" directories and files (starting with a dot).
       
   234      *
       
   235      * @param Boolean $ignoreDotFiles Whether to exclude "hidden" files or not
       
   236      *
       
   237      * @return Finder The current Finder instance
       
   238      *
       
   239      * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
       
   240      *
       
   241      * @api
       
   242      */
       
   243     public function ignoreDotFiles($ignoreDotFiles)
       
   244     {
       
   245         if ($ignoreDotFiles) {
       
   246             $this->ignore = $this->ignore | static::IGNORE_DOT_FILES;
       
   247         } else {
       
   248             $this->ignore = $this->ignore ^ static::IGNORE_DOT_FILES;
       
   249         }
       
   250 
       
   251         return $this;
       
   252     }
       
   253 
       
   254     /**
       
   255      * Forces the finder to ignore version control directories.
       
   256      *
       
   257      * @param Boolean $ignoreVCS Whether to exclude VCS files or not
       
   258      *
       
   259      * @return Finder The current Finder instance
       
   260      *
       
   261      * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
       
   262      *
       
   263      * @api
       
   264      */
       
   265     public function ignoreVCS($ignoreVCS)
       
   266     {
       
   267         if ($ignoreVCS) {
       
   268             $this->ignore = $this->ignore | static::IGNORE_VCS_FILES;
       
   269         } else {
       
   270             $this->ignore = $this->ignore ^ static::IGNORE_VCS_FILES;
       
   271         }
       
   272 
       
   273         return $this;
       
   274     }
       
   275 
       
   276     static public function addVCSPattern($pattern)
       
   277     {
       
   278         static::$vcsPatterns[] = $pattern;
       
   279     }
       
   280 
       
   281     /**
       
   282      * Sorts files and directories by an anonymous function.
       
   283      *
       
   284      * The anonymous function receives two \SplFileInfo instances to compare.
       
   285      *
       
   286      * This can be slow as all the matching files and directories must be retrieved for comparison.
       
   287      *
       
   288      * @param  Closure $closure An anonymous function
       
   289      *
       
   290      * @return Finder The current Finder instance
       
   291      *
       
   292      * @see Symfony\Component\Finder\Iterator\SortableIterator
       
   293      *
       
   294      * @api
       
   295      */
       
   296     public function sort(\Closure $closure)
       
   297     {
       
   298         $this->sort = $closure;
       
   299 
       
   300         return $this;
       
   301     }
       
   302 
       
   303     /**
       
   304      * Sorts files and directories by name.
       
   305      *
       
   306      * This can be slow as all the matching files and directories must be retrieved for comparison.
       
   307      *
       
   308      * @return Finder The current Finder instance
       
   309      *
       
   310      * @see Symfony\Component\Finder\Iterator\SortableIterator
       
   311      *
       
   312      * @api
       
   313      */
       
   314     public function sortByName()
       
   315     {
       
   316         $this->sort = Iterator\SortableIterator::SORT_BY_NAME;
       
   317 
       
   318         return $this;
       
   319     }
       
   320 
       
   321     /**
       
   322      * Sorts files and directories by type (directories before files), then by name.
       
   323      *
       
   324      * This can be slow as all the matching files and directories must be retrieved for comparison.
       
   325      *
       
   326      * @return Finder The current Finder instance
       
   327      *
       
   328      * @see Symfony\Component\Finder\Iterator\SortableIterator
       
   329      *
       
   330      * @api
       
   331      */
       
   332     public function sortByType()
       
   333     {
       
   334         $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
       
   335 
       
   336         return $this;
       
   337     }
       
   338 
       
   339     /**
       
   340      * Filters the iterator with an anonymous function.
       
   341      *
       
   342      * The anonymous function receives a \SplFileInfo and must return false
       
   343      * to remove files.
       
   344      *
       
   345      * @param  Closure $closure An anonymous function
       
   346      *
       
   347      * @return Finder The current Finder instance
       
   348      *
       
   349      * @see Symfony\Component\Finder\Iterator\CustomFilterIterator
       
   350      *
       
   351      * @api
       
   352      */
       
   353     public function filter(\Closure $closure)
       
   354     {
       
   355         $this->filters[] = $closure;
       
   356 
       
   357         return $this;
       
   358     }
       
   359 
       
   360     /**
       
   361      * Forces the following of symlinks.
       
   362      *
       
   363      * @return Finder The current Finder instance
       
   364      *
       
   365      * @api
       
   366      */
       
   367     public function followLinks()
       
   368     {
       
   369         $this->followLinks = true;
       
   370 
       
   371         return $this;
       
   372     }
       
   373 
       
   374     /**
       
   375      * Searches files and directories which match defined rules.
       
   376      *
       
   377      * @param  string|array $dirs A directory path or an array of directories
       
   378      *
       
   379      * @return Finder The current Finder instance
       
   380      *
       
   381      * @throws \InvalidArgumentException if one of the directory does not exist
       
   382      *
       
   383      * @api
       
   384      */
       
   385     public function in($dirs)
       
   386     {
       
   387         $dirs = (array) $dirs;
       
   388 
       
   389         foreach ($dirs as $dir) {
       
   390             if (!is_dir($dir)) {
       
   391                 throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir));
       
   392             }
       
   393         }
       
   394 
       
   395         $this->dirs = array_merge($this->dirs, $dirs);
       
   396 
       
   397         return $this;
       
   398     }
       
   399 
       
   400     /**
       
   401      * Returns an Iterator for the current Finder configuration.
       
   402      *
       
   403      * This method implements the IteratorAggregate interface.
       
   404      *
       
   405      * @return \Iterator An iterator
       
   406      *
       
   407      * @throws \LogicException if the in() method has not been called
       
   408      */
       
   409     public function getIterator()
       
   410     {
       
   411         if (0 === count($this->dirs)) {
       
   412             throw new \LogicException('You must call the in() method before iterating over a Finder.');
       
   413         }
       
   414 
       
   415         if (1 === count($this->dirs) && 0 === count($this->iterators)) {
       
   416             return $this->searchInDirectory($this->dirs[0]);
       
   417         }
       
   418 
       
   419         $iterator = new \AppendIterator();
       
   420         foreach ($this->dirs as $dir) {
       
   421             $iterator->append($this->searchInDirectory($dir));
       
   422         }
       
   423 
       
   424         foreach ($this->iterators as $it) {
       
   425             $iterator->append($it);
       
   426         }
       
   427 
       
   428         return $iterator;
       
   429     }
       
   430 
       
   431     /**
       
   432      * Appends an existing set of files/directories to the finder.
       
   433      *
       
   434      * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
       
   435      *
       
   436      * @param mixed $iterator
       
   437      */
       
   438     public function append($iterator)
       
   439     {
       
   440         if ($iterator instanceof \IteratorAggregate) {
       
   441             $this->iterators[] = $iterator->getIterator();
       
   442         } elseif ($iterator instanceof \Iterator) {
       
   443             $this->iterators[] = $iterator;
       
   444         } elseif ($iterator instanceof \Traversable || is_array($iterator)) {
       
   445             $it = new \ArrayIterator();
       
   446             foreach ($iterator as $file) {
       
   447                 $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
       
   448             }
       
   449             $this->iterators[] = $it;
       
   450         } else {
       
   451             throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
       
   452         }
       
   453     }
       
   454 
       
   455     private function searchInDirectory($dir)
       
   456     {
       
   457         $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
       
   458 
       
   459         if ($this->followLinks) {
       
   460             $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
       
   461         }
       
   462 
       
   463         $iterator = new \RecursiveIteratorIterator(
       
   464             new Iterator\RecursiveDirectoryIterator($dir, $flags),
       
   465             \RecursiveIteratorIterator::SELF_FIRST
       
   466         );
       
   467 
       
   468         if ($this->depths) {
       
   469             $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->depths);
       
   470         }
       
   471 
       
   472         if ($this->mode) {
       
   473             $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
       
   474         }
       
   475 
       
   476         if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
       
   477             $this->exclude = array_merge($this->exclude, static::$vcsPatterns);
       
   478         }
       
   479 
       
   480         if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
       
   481             $this->exclude[] = '\..+';
       
   482         }
       
   483 
       
   484         if ($this->exclude) {
       
   485             $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
       
   486         }
       
   487 
       
   488         if ($this->names || $this->notNames) {
       
   489             $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
       
   490         }
       
   491 
       
   492         if ($this->sizes) {
       
   493             $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
       
   494         }
       
   495 
       
   496         if ($this->dates) {
       
   497             $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
       
   498         }
       
   499 
       
   500         if ($this->filters) {
       
   501             $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
       
   502         }
       
   503 
       
   504         if ($this->sort) {
       
   505             $iterator = new Iterator\SortableIterator($iterator, $this->sort);
       
   506         }
       
   507 
       
   508         return $iterator;
       
   509     }
       
   510 }