vendor/symfony/finder/Finder.php line 435

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