Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Finder; Chris@0: Chris@0: use Symfony\Component\Finder\Comparator\DateComparator; Chris@0: use Symfony\Component\Finder\Comparator\NumberComparator; Chris@0: use Symfony\Component\Finder\Iterator\CustomFilterIterator; Chris@0: use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; Chris@0: use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; Chris@0: use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; Chris@0: use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; Chris@0: use Symfony\Component\Finder\Iterator\FilenameFilterIterator; Chris@0: use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; Chris@0: use Symfony\Component\Finder\Iterator\SortableIterator; Chris@0: Chris@0: /** Chris@0: * Finder allows to build rules to find files and directories. Chris@0: * Chris@0: * It is a thin wrapper around several specialized iterator classes. Chris@0: * Chris@0: * All rules may be invoked several times. Chris@0: * Chris@18: * All methods return the current Finder object to allow chaining: Chris@0: * Chris@17: * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); Chris@0: * Chris@0: * @author Fabien Potencier Chris@0: */ Chris@0: class Finder implements \IteratorAggregate, \Countable Chris@0: { Chris@0: const IGNORE_VCS_FILES = 1; Chris@0: const IGNORE_DOT_FILES = 2; Chris@0: Chris@0: private $mode = 0; Chris@17: private $names = []; Chris@17: private $notNames = []; Chris@17: private $exclude = []; Chris@17: private $filters = []; Chris@17: private $depths = []; Chris@17: private $sizes = []; Chris@0: private $followLinks = false; Chris@0: private $sort = false; Chris@0: private $ignore = 0; Chris@17: private $dirs = []; Chris@17: private $dates = []; Chris@17: private $iterators = []; Chris@17: private $contains = []; Chris@17: private $notContains = []; Chris@17: private $paths = []; Chris@17: private $notPaths = []; Chris@0: private $ignoreUnreadableDirs = false; Chris@0: Chris@17: private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; Chris@0: Chris@0: public function __construct() Chris@0: { Chris@0: $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a new Finder. Chris@0: * Chris@0: * @return static Chris@0: */ Chris@0: public static function create() Chris@0: { Chris@0: return new static(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Restricts the matching to directories only. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function directories() Chris@0: { Chris@0: $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Restricts the matching to files only. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function files() Chris@0: { Chris@0: $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds tests for the directory depth. Chris@0: * Chris@0: * Usage: Chris@0: * Chris@17: * $finder->depth('> 1') // the Finder will start matching at level 1. Chris@17: * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. Chris@0: * Chris@0: * @param string|int $level The depth level expression Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see DepthRangeFilterIterator Chris@0: * @see NumberComparator Chris@0: */ Chris@0: public function depth($level) Chris@0: { Chris@0: $this->depths[] = new Comparator\NumberComparator($level); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds tests for file dates (last modified). Chris@0: * Chris@0: * The date must be something that strtotime() is able to parse: Chris@0: * Chris@17: * $finder->date('since yesterday'); Chris@17: * $finder->date('until 2 days ago'); Chris@17: * $finder->date('> now - 2 hours'); Chris@17: * $finder->date('>= 2005-10-15'); Chris@0: * Chris@0: * @param string $date A date range string Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see strtotime Chris@0: * @see DateRangeFilterIterator Chris@0: * @see DateComparator Chris@0: */ Chris@0: public function date($date) Chris@0: { Chris@0: $this->dates[] = new Comparator\DateComparator($date); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds rules that files must match. Chris@0: * Chris@0: * You can use patterns (delimited with / sign), globs or simple strings. Chris@0: * Chris@17: * $finder->name('*.php') Chris@17: * $finder->name('/\.php$/') // same as above Chris@17: * $finder->name('test.php') Chris@0: * Chris@0: * @param string $pattern A pattern (a regexp, a glob, or a string) Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see FilenameFilterIterator Chris@0: */ Chris@0: public function name($pattern) Chris@0: { Chris@0: $this->names[] = $pattern; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds rules that files must not match. Chris@0: * Chris@0: * @param string $pattern A pattern (a regexp, a glob, or a string) Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see FilenameFilterIterator Chris@0: */ Chris@0: public function notName($pattern) Chris@0: { Chris@0: $this->notNames[] = $pattern; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds tests that file contents must match. Chris@0: * Chris@0: * Strings or PCRE patterns can be used: Chris@0: * Chris@17: * $finder->contains('Lorem ipsum') Chris@17: * $finder->contains('/Lorem ipsum/i') Chris@0: * Chris@0: * @param string $pattern A pattern (string or regexp) Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see FilecontentFilterIterator Chris@0: */ Chris@0: public function contains($pattern) Chris@0: { Chris@0: $this->contains[] = $pattern; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds tests that file contents must not match. Chris@0: * Chris@0: * Strings or PCRE patterns can be used: Chris@0: * Chris@17: * $finder->notContains('Lorem ipsum') Chris@17: * $finder->notContains('/Lorem ipsum/i') Chris@0: * Chris@0: * @param string $pattern A pattern (string or regexp) Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see FilecontentFilterIterator Chris@0: */ Chris@0: public function notContains($pattern) Chris@0: { Chris@0: $this->notContains[] = $pattern; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds rules that filenames must match. Chris@0: * Chris@0: * You can use patterns (delimited with / sign) or simple strings. Chris@0: * Chris@17: * $finder->path('some/special/dir') Chris@17: * $finder->path('/some\/special\/dir/') // same as above Chris@0: * Chris@0: * Use only / as dirname separator. Chris@0: * Chris@0: * @param string $pattern A pattern (a regexp or a string) Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see FilenameFilterIterator Chris@0: */ Chris@0: public function path($pattern) Chris@0: { Chris@0: $this->paths[] = $pattern; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds rules that filenames must not match. Chris@0: * Chris@0: * You can use patterns (delimited with / sign) or simple strings. Chris@0: * Chris@17: * $finder->notPath('some/special/dir') Chris@17: * $finder->notPath('/some\/special\/dir/') // same as above Chris@0: * Chris@0: * Use only / as dirname separator. Chris@0: * Chris@0: * @param string $pattern A pattern (a regexp or a string) Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see FilenameFilterIterator Chris@0: */ Chris@0: public function notPath($pattern) Chris@0: { Chris@0: $this->notPaths[] = $pattern; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds tests for file sizes. Chris@0: * Chris@17: * $finder->size('> 10K'); Chris@17: * $finder->size('<= 1Ki'); Chris@17: * $finder->size(4); Chris@0: * Chris@0: * @param string|int $size A size range string or an integer Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see SizeRangeFilterIterator Chris@0: * @see NumberComparator Chris@0: */ Chris@0: public function size($size) Chris@0: { Chris@0: $this->sizes[] = new Comparator\NumberComparator($size); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Excludes directories. Chris@0: * Chris@13: * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: Chris@13: * Chris@13: * $finder->in(__DIR__)->exclude('ruby'); Chris@13: * Chris@0: * @param string|array $dirs A directory path or an array of directories Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see ExcludeDirectoryFilterIterator Chris@0: */ Chris@0: public function exclude($dirs) Chris@0: { Chris@0: $this->exclude = array_merge($this->exclude, (array) $dirs); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Excludes "hidden" directories and files (starting with a dot). Chris@0: * Chris@13: * This option is enabled by default. Chris@13: * Chris@0: * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see ExcludeDirectoryFilterIterator Chris@0: */ Chris@0: public function ignoreDotFiles($ignoreDotFiles) Chris@0: { Chris@0: if ($ignoreDotFiles) { Chris@0: $this->ignore |= static::IGNORE_DOT_FILES; Chris@0: } else { Chris@0: $this->ignore &= ~static::IGNORE_DOT_FILES; Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Forces the finder to ignore version control directories. Chris@0: * Chris@13: * This option is enabled by default. Chris@13: * Chris@0: * @param bool $ignoreVCS Whether to exclude VCS files or not Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see ExcludeDirectoryFilterIterator Chris@0: */ Chris@0: public function ignoreVCS($ignoreVCS) Chris@0: { Chris@0: if ($ignoreVCS) { Chris@0: $this->ignore |= static::IGNORE_VCS_FILES; Chris@0: } else { Chris@0: $this->ignore &= ~static::IGNORE_VCS_FILES; Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds VCS patterns. Chris@0: * Chris@0: * @see ignoreVCS() Chris@0: * Chris@0: * @param string|string[] $pattern VCS patterns to ignore Chris@0: */ Chris@0: public static function addVCSPattern($pattern) Chris@0: { Chris@0: foreach ((array) $pattern as $p) { Chris@0: self::$vcsPatterns[] = $p; Chris@0: } Chris@0: Chris@0: self::$vcsPatterns = array_unique(self::$vcsPatterns); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sorts files and directories by an anonymous function. Chris@0: * Chris@0: * The anonymous function receives two \SplFileInfo instances to compare. Chris@0: * Chris@0: * This can be slow as all the matching files and directories must be retrieved for comparison. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see SortableIterator Chris@0: */ Chris@0: public function sort(\Closure $closure) Chris@0: { Chris@0: $this->sort = $closure; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sorts files and directories by name. Chris@0: * Chris@0: * This can be slow as all the matching files and directories must be retrieved for comparison. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see SortableIterator Chris@0: */ Chris@0: public function sortByName() Chris@0: { Chris@0: $this->sort = Iterator\SortableIterator::SORT_BY_NAME; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sorts files and directories by type (directories before files), then by name. Chris@0: * Chris@0: * This can be slow as all the matching files and directories must be retrieved for comparison. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see SortableIterator Chris@0: */ Chris@0: public function sortByType() Chris@0: { Chris@0: $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sorts files and directories by the last accessed time. Chris@0: * Chris@0: * This is the time that the file was last accessed, read or written to. Chris@0: * Chris@0: * This can be slow as all the matching files and directories must be retrieved for comparison. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see SortableIterator Chris@0: */ Chris@0: public function sortByAccessedTime() Chris@0: { Chris@0: $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sorts files and directories by the last inode changed time. Chris@0: * Chris@0: * This is the time that the inode information was last modified (permissions, owner, group or other metadata). Chris@0: * Chris@0: * On Windows, since inode is not available, changed time is actually the file creation time. Chris@0: * Chris@0: * This can be slow as all the matching files and directories must be retrieved for comparison. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see SortableIterator Chris@0: */ Chris@0: public function sortByChangedTime() Chris@0: { Chris@0: $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sorts files and directories by the last modified time. Chris@0: * Chris@0: * This is the last time the actual contents of the file were last modified. Chris@0: * Chris@0: * This can be slow as all the matching files and directories must be retrieved for comparison. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see SortableIterator Chris@0: */ Chris@0: public function sortByModifiedTime() Chris@0: { Chris@0: $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Filters the iterator with an anonymous function. Chris@0: * Chris@0: * The anonymous function receives a \SplFileInfo and must return false Chris@0: * to remove files. Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @see CustomFilterIterator Chris@0: */ Chris@0: public function filter(\Closure $closure) Chris@0: { Chris@0: $this->filters[] = $closure; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Forces the following of symlinks. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function followLinks() Chris@0: { Chris@0: $this->followLinks = true; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tells finder to ignore unreadable directories. Chris@0: * Chris@0: * By default, scanning unreadable directories content throws an AccessDeniedException. Chris@0: * Chris@0: * @param bool $ignore Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function ignoreUnreadableDirs($ignore = true) Chris@0: { Chris@0: $this->ignoreUnreadableDirs = (bool) $ignore; Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Searches files and directories which match defined rules. Chris@0: * Chris@0: * @param string|array $dirs A directory path or an array of directories Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @throws \InvalidArgumentException if one of the directories does not exist Chris@0: */ Chris@0: public function in($dirs) Chris@0: { Chris@17: $resolvedDirs = []; Chris@0: Chris@0: foreach ((array) $dirs as $dir) { Chris@0: if (is_dir($dir)) { Chris@13: $resolvedDirs[] = $this->normalizeDir($dir); Chris@17: } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) { Chris@17: $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob)); Chris@0: } else { Chris@0: throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); Chris@0: } Chris@0: } Chris@0: Chris@0: $this->dirs = array_merge($this->dirs, $resolvedDirs); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns an Iterator for the current Finder configuration. Chris@0: * Chris@0: * This method implements the IteratorAggregate interface. Chris@0: * Chris@0: * @return \Iterator|SplFileInfo[] An iterator Chris@0: * Chris@0: * @throws \LogicException if the in() method has not been called Chris@0: */ Chris@0: public function getIterator() Chris@0: { Chris@17: if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { Chris@0: throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); Chris@0: } Chris@0: Chris@17: if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { Chris@0: return $this->searchInDirectory($this->dirs[0]); Chris@0: } Chris@0: Chris@0: $iterator = new \AppendIterator(); Chris@0: foreach ($this->dirs as $dir) { Chris@0: $iterator->append($this->searchInDirectory($dir)); Chris@0: } Chris@0: Chris@0: foreach ($this->iterators as $it) { Chris@0: $iterator->append($it); Chris@0: } Chris@0: Chris@0: return $iterator; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Appends an existing set of files/directories to the finder. Chris@0: * Chris@0: * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. Chris@0: * Chris@17: * @param iterable $iterator Chris@0: * Chris@0: * @return $this Chris@0: * Chris@0: * @throws \InvalidArgumentException when the given argument is not iterable Chris@0: */ Chris@0: public function append($iterator) Chris@0: { Chris@0: if ($iterator instanceof \IteratorAggregate) { Chris@0: $this->iterators[] = $iterator->getIterator(); Chris@0: } elseif ($iterator instanceof \Iterator) { Chris@0: $this->iterators[] = $iterator; Chris@17: } elseif ($iterator instanceof \Traversable || \is_array($iterator)) { Chris@0: $it = new \ArrayIterator(); Chris@0: foreach ($iterator as $file) { Chris@0: $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); Chris@0: } Chris@0: $this->iterators[] = $it; Chris@0: } else { Chris@0: throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@12: * Check if the any results were found. Chris@12: * Chris@12: * @return bool Chris@12: */ Chris@12: public function hasResults() Chris@12: { Chris@12: foreach ($this->getIterator() as $_) { Chris@12: return true; Chris@12: } Chris@12: Chris@12: return false; Chris@12: } Chris@12: Chris@12: /** Chris@0: * Counts all the results collected by the iterators. Chris@0: * Chris@0: * @return int Chris@0: */ Chris@0: public function count() Chris@0: { Chris@0: return iterator_count($this->getIterator()); Chris@0: } Chris@0: Chris@0: /** Chris@17: * @param string $dir Chris@0: * Chris@0: * @return \Iterator Chris@0: */ Chris@0: private function searchInDirectory($dir) Chris@0: { Chris@18: $exclude = $this->exclude; Chris@18: $notPaths = $this->notPaths; Chris@18: Chris@0: if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { Chris@18: $exclude = array_merge($exclude, self::$vcsPatterns); Chris@0: } Chris@0: Chris@0: if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { Chris@18: $notPaths[] = '#(^|/)\..+(/|$)#'; Chris@0: } Chris@0: Chris@0: $minDepth = 0; Chris@0: $maxDepth = PHP_INT_MAX; Chris@0: Chris@0: foreach ($this->depths as $comparator) { Chris@0: switch ($comparator->getOperator()) { Chris@0: case '>': Chris@0: $minDepth = $comparator->getTarget() + 1; Chris@0: break; Chris@0: case '>=': Chris@0: $minDepth = $comparator->getTarget(); Chris@0: break; Chris@0: case '<': Chris@0: $maxDepth = $comparator->getTarget() - 1; Chris@0: break; Chris@0: case '<=': Chris@0: $maxDepth = $comparator->getTarget(); Chris@0: break; Chris@0: default: Chris@0: $minDepth = $maxDepth = $comparator->getTarget(); Chris@0: } Chris@0: } Chris@0: Chris@0: $flags = \RecursiveDirectoryIterator::SKIP_DOTS; Chris@0: Chris@0: if ($this->followLinks) { Chris@0: $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; Chris@0: } Chris@0: Chris@0: $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); Chris@0: Chris@18: if ($exclude) { Chris@18: $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); Chris@0: } Chris@0: Chris@0: $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); Chris@0: Chris@0: if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) { Chris@0: $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); Chris@0: } Chris@0: Chris@0: if ($this->mode) { Chris@0: $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); Chris@0: } Chris@0: Chris@0: if ($this->names || $this->notNames) { Chris@0: $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); Chris@0: } Chris@0: Chris@0: if ($this->contains || $this->notContains) { Chris@0: $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); Chris@0: } Chris@0: Chris@0: if ($this->sizes) { Chris@0: $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); Chris@0: } Chris@0: Chris@0: if ($this->dates) { Chris@0: $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); Chris@0: } Chris@0: Chris@0: if ($this->filters) { Chris@0: $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); Chris@0: } Chris@0: Chris@18: if ($this->paths || $notPaths) { Chris@18: $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); Chris@0: } Chris@0: Chris@0: if ($this->sort) { Chris@0: $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); Chris@0: $iterator = $iteratorAggregate->getIterator(); Chris@0: } Chris@0: Chris@0: return $iterator; Chris@0: } Chris@13: Chris@13: /** Chris@13: * Normalizes given directory names by removing trailing slashes. Chris@13: * Chris@17: * Excluding: (s)ftp:// wrapper Chris@17: * Chris@13: * @param string $dir Chris@13: * Chris@13: * @return string Chris@13: */ Chris@13: private function normalizeDir($dir) Chris@13: { Chris@17: $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); Chris@17: Chris@17: if (preg_match('#^s?ftp://#', $dir)) { Chris@17: $dir .= '/'; Chris@17: } Chris@17: Chris@17: return $dir; Chris@13: } Chris@0: }