vendor/symfony/finder/Finder.php line 608

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