Showing preview only (259K chars total). Download the full file or copy to clipboard to get everything.
Repository: symfony/finder
Branch: 8.1
Commit: e4b85e359203
Files: 85
Total size: 237.5 KB
Directory structure:
gitextract_xqh0_mj_/
├── .gitattributes
├── .github/
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── close-pull-request.yml
├── .gitignore
├── CHANGELOG.md
├── Comparator/
│ ├── Comparator.php
│ ├── DateComparator.php
│ └── NumberComparator.php
├── Exception/
│ ├── AccessDeniedException.php
│ └── DirectoryNotFoundException.php
├── Finder.php
├── Gitignore.php
├── Glob.php
├── Iterator/
│ ├── CustomFilterIterator.php
│ ├── DateRangeFilterIterator.php
│ ├── DepthRangeFilterIterator.php
│ ├── ExcludeDirectoryFilterIterator.php
│ ├── FileTypeFilterIterator.php
│ ├── FilecontentFilterIterator.php
│ ├── FilenameFilterIterator.php
│ ├── LazyIterator.php
│ ├── MultiplePcreFilterIterator.php
│ ├── PathFilterIterator.php
│ ├── RecursiveDirectoryIterator.php
│ ├── SizeRangeFilterIterator.php
│ ├── SortableIterator.php
│ └── VcsIgnoredFilterIterator.php
├── LICENSE
├── README.md
├── SplFileInfo.php
├── Tests/
│ ├── Comparator/
│ │ ├── ComparatorTest.php
│ │ ├── DateComparatorTest.php
│ │ └── NumberComparatorTest.php
│ ├── FinderOpenBasedirTest.php
│ ├── FinderTest.php
│ ├── Fixtures/
│ │ ├── .dot/
│ │ │ ├── a
│ │ │ └── b/
│ │ │ ├── c.neon
│ │ │ └── d.neon
│ │ ├── copy/
│ │ │ └── A/
│ │ │ ├── B/
│ │ │ │ ├── C/
│ │ │ │ │ └── abc.dat.copy
│ │ │ │ └── ab.dat.copy
│ │ │ └── a.dat.copy
│ │ ├── dolor.txt
│ │ ├── gitignore/
│ │ │ ├── .gitignore
│ │ │ ├── git_root/
│ │ │ │ └── search_root/
│ │ │ │ ├── .gitignore
│ │ │ │ ├── b.txt
│ │ │ │ └── dir/
│ │ │ │ ├── .gitignore
│ │ │ │ └── a.txt
│ │ │ └── search_root/
│ │ │ ├── .gitignore
│ │ │ ├── a.txt
│ │ │ ├── b.txt
│ │ │ └── dir/
│ │ │ ├── .gitignore
│ │ │ ├── a.txt
│ │ │ └── b.txt
│ │ ├── ipsum.txt
│ │ ├── lorem.txt
│ │ ├── one/
│ │ │ ├── .dot
│ │ │ ├── a
│ │ │ └── b/
│ │ │ ├── c.neon
│ │ │ └── d.neon
│ │ └── with space/
│ │ └── foo.txt
│ ├── GitignoreTest.php
│ ├── GlobTest.php
│ └── Iterator/
│ ├── CustomFilterIteratorTest.php
│ ├── DateRangeFilterIteratorTest.php
│ ├── DepthRangeFilterIteratorTest.php
│ ├── ExcludeDirectoryFilterIteratorTest.php
│ ├── FileTypeFilterIteratorTest.php
│ ├── FilecontentFilterIteratorTest.php
│ ├── FilenameFilterIteratorTest.php
│ ├── InnerNameIterator.php
│ ├── Iterator.php
│ ├── IteratorTestCase.php
│ ├── LazyIteratorTest.php
│ ├── MockFileListIterator.php
│ ├── MockSplFileInfo.php
│ ├── MultiplePcreFilterIteratorTest.php
│ ├── PathFilterIteratorTest.php
│ ├── RealIteratorTestCase.php
│ ├── RecursiveDirectoryIteratorTest.php
│ ├── SizeRangeFilterIteratorTest.php
│ ├── SortableIteratorTest.php
│ ├── VcsIgnoredFilterIteratorTest.php
│ └── VfsIteratorTestTrait.php
├── composer.json
└── phpunit.xml.dist
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
/Tests export-ignore
/phpunit.xml.dist export-ignore
/.git* export-ignore
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
Please do not submit any Pull Requests here. They will be closed.
---
Please submit your PR here instead:
https://github.com/symfony/symfony
This repository is what we call a "subtree split": a read-only subset of that main repository.
We're looking forward to your PR there!
================================================
FILE: .github/workflows/close-pull-request.yml
================================================
name: Close Pull Request
on:
pull_request_target:
types: [opened]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: |
Thanks for your Pull Request! We love contributions.
However, you should instead open your PR on the main repository:
https://github.com/symfony/symfony
This repository is what we call a "subtree split": a read-only subset of that main repository.
We're looking forward to your PR there!
================================================
FILE: .gitignore
================================================
vendor/
composer.lock
phpunit.xml
================================================
FILE: CHANGELOG.md
================================================
CHANGELOG
=========
6.4
---
* Add early directory pruning to `Finder::filter()`
6.2
---
* Add `Finder::sortByExtension()` and `Finder::sortBySize()`
* Add `Finder::sortByCaseInsensitiveName()` to sort by name with case insensitive sorting methods
6.0
---
* Remove `Comparator::setTarget()` and `Comparator::setOperator()`
5.4.0
-----
* Deprecate `Comparator::setTarget()` and `Comparator::setOperator()`
* Add a constructor to `Comparator` that allows setting target and operator
* Finder's iterator has now `Symfony\Component\Finder\SplFileInfo` inner type specified
* Add recursive .gitignore files support
5.0.0
-----
* added `$useNaturalSort` argument to `Finder::sortByName()`
4.3.0
-----
* added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore
4.2.0
-----
* added $useNaturalSort option to Finder::sortByName() method
* the `Finder::sortByName()` method will have a new `$useNaturalSort`
argument in version 5.0, not defining it is deprecated
* added `Finder::reverseSorting()` to reverse the sorting
4.0.0
-----
* removed `ExceptionInterface`
* removed `Symfony\Component\Finder\Iterator\FilterIterator`
3.4.0
-----
* deprecated `Symfony\Component\Finder\Iterator\FilterIterator`
* added Finder::hasResults() method to check if any results were found
3.3.0
-----
* added double-star matching to Glob::toRegex()
3.0.0
-----
* removed deprecated classes
2.8.0
-----
* deprecated adapters and related classes
2.5.0
-----
* added support for GLOB_BRACE in the paths passed to Finder::in()
2.3.0
-----
* added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs())
* unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception
2.2.0
-----
* added Finder::path() and Finder::notPath() methods
* added finder adapters to improve performance on specific platforms
* added support for wildcard characters (glob patterns) in the paths passed
to Finder::in()
2.1.0
-----
* added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and
Finder::sortByModifiedTime()
* added Countable to Finder
* added support for an array of directories as an argument to
Finder::exclude()
* added searching based on the file content via Finder::contains() and
Finder::notContains()
* added support for the != operator in the Comparator
* [BC BREAK] filter expressions (used for file name and content) are no more
considered as regexps but glob patterns when they are enclosed in '*' or '?'
================================================
FILE: Comparator/Comparator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Comparator
{
private string $operator;
public function __construct(
private string $target,
string $operator = '==',
) {
if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='], true)) {
throw new \InvalidArgumentException(\sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
}
/**
* Gets the target value.
*/
public function getTarget(): string
{
return $this->target;
}
/**
* Gets the comparison operator.
*/
public function getOperator(): string
{
return $this->operator;
}
/**
* Tests against the target.
*/
public function test(mixed $test): bool
{
return match ($this->operator) {
'>' => $test > $this->target,
'>=' => $test >= $this->target,
'<' => $test < $this->target,
'<=' => $test <= $this->target,
'!=' => $test != $this->target,
default => $test == $this->target,
};
}
}
================================================
FILE: Comparator/DateComparator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* DateCompare compiles date comparisons.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateComparator extends Comparator
{
/**
* @param string $test A comparison string
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a date test.', $test));
}
try {
$date = new \DateTimeImmutable($matches[2]);
$target = $date->format('U');
} catch (\Exception) {
throw new \InvalidArgumentException(\sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = $matches[1] ?: '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
if ('until' === $operator || 'before' === $operator) {
$operator = '<';
}
parent::__construct($target, $operator);
}
}
================================================
FILE: Comparator/NumberComparator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* NumberComparator compiles a simple comparison to an anonymous
* subroutine, which you can call with a value to be tested again.
*
* Now this would be very pointless, if NumberCompare didn't understand
* magnitudes.
*
* The target value may use magnitudes of kilobytes (k, ki),
* megabytes (m, mi), or gigabytes (g, gi). Those suffixed
* with an i use the appropriate 2**n version in accordance with the
* IEC standard: http://physics.nist.gov/cuu/Units/binary.html
*
* Based on the Perl Number::Compare module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*
* @see http://physics.nist.gov/cuu/Units/binary.html
*/
class NumberComparator extends Comparator
{
/**
* @param string|null $test A comparison string or null
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(?string $test)
{
if (null === $test || !preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(\sprintf('Don\'t understand "%s" as a number test.', $test ?? 'null'));
}
$target = $matches[2];
if (!is_numeric($target)) {
throw new \InvalidArgumentException(\sprintf('Invalid number "%s".', $target));
}
if (isset($matches[3])) {
// magnitude
switch (strtolower($matches[3])) {
case 'k':
$target *= 1000;
break;
case 'ki':
$target *= 1024;
break;
case 'm':
$target *= 1000000;
break;
case 'mi':
$target *= 1024 * 1024;
break;
case 'g':
$target *= 1000000000;
break;
case 'gi':
$target *= 1024 * 1024 * 1024;
break;
}
}
parent::__construct($target, $matches[1] ?: '==');
}
}
================================================
FILE: Exception/AccessDeniedException.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class AccessDeniedException extends \UnexpectedValueException
{
}
================================================
FILE: Exception/DirectoryNotFoundException.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Andreas Erhard <andreas.erhard@i-med.ac.at>
*/
class DirectoryNotFoundException extends \InvalidArgumentException
{
}
================================================
FILE: Finder.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
use Symfony\Component\Finder\Iterator\LazyIterator;
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Iterator\SortableIterator;
/**
* Finder allows to build rules to find files and directories.
*
* It is a thin wrapper around several specialized iterator classes.
*
* All rules may be invoked several times.
*
* All methods return the current Finder object to allow chaining:
*
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<non-empty-string, SplFileInfo>
*/
class Finder implements \IteratorAggregate, \Countable
{
public const IGNORE_VCS_FILES = 1;
public const IGNORE_DOT_FILES = 2;
public const IGNORE_VCS_IGNORED_FILES = 4;
private int $mode = 0;
private array $names = [];
private array $notNames = [];
private array $exclude = [];
private array $filters = [];
private array $pruneFilters = [];
private array $depths = [];
private array $sizes = [];
private bool $followLinks = false;
private bool $unixPaths = false;
private bool $reverseSorting = false;
private \Closure|int|false $sort = false;
private int $ignore = 0;
/** @var list<string> */
private array $dirs = [];
private array $dates = [];
/** @var list<iterable<SplFileInfo|\SplFileInfo|string>> */
private array $iterators = [];
private array $contains = [];
private array $notContains = [];
private array $paths = [];
private array $notPaths = [];
private bool $ignoreUnreadableDirs = false;
private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
public function __construct()
{
$this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
}
/**
* Creates a new Finder.
*/
public static function create(): static
{
return new static();
}
/**
* Restricts the matching to directories only.
*
* @return $this
*/
public function directories(): static
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
return $this;
}
/**
* Restricts the matching to files only.
*
* @return $this
*/
public function files(): static
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
return $this;
}
/**
* Adds tests for the directory depth.
*
* Usage:
*
* $finder->depth('> 1') // the Finder will start matching at level 1.
* $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
* $finder->depth(['>= 1', '< 3'])
*
* @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
*
* @return $this
*
* @see DepthRangeFilterIterator
* @see NumberComparator
*/
public function depth(string|int|array $levels): static
{
foreach ((array) $levels as $level) {
$this->depths[] = new NumberComparator($level);
}
return $this;
}
/**
* Adds tests for file dates (last modified).
*
* The date must be something that strtotime() is able to parse:
*
* $finder->date('since yesterday');
* $finder->date('until 2 days ago');
* $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15');
* $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
*
* @param string|string[] $dates A date range string or an array of date ranges
*
* @return $this
*
* @see strtotime
* @see DateRangeFilterIterator
* @see DateComparator
*/
public function date(string|array $dates): static
{
foreach ((array) $dates as $date) {
$this->dates[] = new DateComparator($date);
}
return $this;
}
/**
* Adds rules that files must match.
*
* You can use patterns (delimited with / sign), globs or simple strings.
*
* $finder->name('/\.php$/')
* $finder->name('*.php') // same as above, without dot files
* $finder->name('test.php')
* $finder->name(['test.py', 'test.php'])
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function name(string|array $patterns): static
{
$this->names = array_merge($this->names, (array) $patterns);
return $this;
}
/**
* Adds rules that files must not match.
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notName(string|array $patterns): static
{
$this->notNames = array_merge($this->notNames, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must match.
*
* Strings or PCRE patterns can be used:
*
* $finder->contains('Lorem ipsum')
* $finder->contains('/Lorem ipsum/i')
* $finder->contains(['dolor', '/ipsum/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function contains(string|array $patterns): static
{
$this->contains = array_merge($this->contains, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must not match.
*
* Strings or PCRE patterns can be used:
*
* $finder->notContains('Lorem ipsum')
* $finder->notContains('/Lorem ipsum/i')
* $finder->notContains(['lorem', '/dolor/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function notContains(string|array $patterns): static
{
$this->notContains = array_merge($this->notContains, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->path('some/special/dir')
* $finder->path('/some\/special\/dir/') // same as above
* $finder->path(['some dir', 'another/dir'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function path(string|array $patterns): static
{
$this->paths = array_merge($this->paths, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must not match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->notPath('some/special/dir')
* $finder->notPath('/some\/special\/dir/') // same as above
* $finder->notPath(['some/file.txt', 'another/file.log'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notPath(string|array $patterns): static
{
$this->notPaths = array_merge($this->notPaths, (array) $patterns);
return $this;
}
/**
* Adds tests for file sizes.
*
* $finder->size('> 10K');
* $finder->size('<= 1Ki');
* $finder->size(4);
* $finder->size(['> 10K', '< 20K'])
*
* @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
*
* @return $this
*
* @see SizeRangeFilterIterator
* @see NumberComparator
*/
public function size(string|int|array $sizes): static
{
foreach ((array) $sizes as $size) {
$this->sizes[] = new NumberComparator($size);
}
return $this;
}
/**
* Excludes directories.
*
* Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
*
* $finder->in(__DIR__)->exclude('ruby');
*
* @param string|array $dirs A directory path or an array of directories
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function exclude(string|array $dirs): static
{
$this->exclude = array_merge($this->exclude, (array) $dirs);
return $this;
}
/**
* Excludes "hidden" directories and files (starting with a dot).
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreDotFiles(bool $ignoreDotFiles): static
{
if ($ignoreDotFiles) {
$this->ignore |= static::IGNORE_DOT_FILES;
} else {
$this->ignore &= ~static::IGNORE_DOT_FILES;
}
return $this;
}
/**
* Forces the finder to ignore version control directories.
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreVCS(bool $ignoreVCS): static
{
if ($ignoreVCS) {
$this->ignore |= static::IGNORE_VCS_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_FILES;
}
return $this;
}
/**
* Forces Finder to obey .gitignore and ignore files based on rules listed there.
*
* This option is disabled by default.
*
* @return $this
*/
public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static
{
if ($ignoreVCSIgnored) {
$this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
}
return $this;
}
/**
* Adds VCS patterns.
*
* @see ignoreVCS()
*
* @param string|string[] $pattern VCS patterns to ignore
*/
public static function addVCSPattern(string|array $pattern): void
{
foreach ((array) $pattern as $p) {
self::$vcsPatterns[] = $p;
}
self::$vcsPatterns = array_unique(self::$vcsPatterns);
}
/**
* Sorts files and directories by an anonymous function.
*
* The anonymous function receives two \SplFileInfo instances to compare.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sort(\Closure $closure): static
{
$this->sort = $closure;
return $this;
}
/**
* Sorts files and directories by extension.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByExtension(): static
{
$this->sort = SortableIterator::SORT_BY_EXTENSION;
return $this;
}
/**
* Sorts files and directories by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByName(bool $useNaturalSort = false): static
{
$this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL : SortableIterator::SORT_BY_NAME;
return $this;
}
/**
* Sorts files and directories by name case insensitive.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static
{
$this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE;
return $this;
}
/**
* Sorts files and directories by size.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortBySize(): static
{
$this->sort = SortableIterator::SORT_BY_SIZE;
return $this;
}
/**
* Sorts files and directories by type (directories before files), then by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByType(): static
{
$this->sort = SortableIterator::SORT_BY_TYPE;
return $this;
}
/**
* Sorts files and directories by the last accessed time.
*
* This is the time that the file was last accessed, read or written to.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByAccessedTime(): static
{
$this->sort = SortableIterator::SORT_BY_ACCESSED_TIME;
return $this;
}
/**
* Reverses the sorting.
*
* @return $this
*/
public function reverseSorting(): static
{
$this->reverseSorting = true;
return $this;
}
/**
* Sorts files and directories by the last inode changed time.
*
* This is the time that the inode information was last modified (permissions, owner, group or other metadata).
*
* On Windows, since inode is not available, changed time is actually the file creation time.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByChangedTime(): static
{
$this->sort = SortableIterator::SORT_BY_CHANGED_TIME;
return $this;
}
/**
* Sorts files and directories by the last modified time.
*
* This is the last time the actual contents of the file were last modified.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByModifiedTime(): static
{
$this->sort = SortableIterator::SORT_BY_MODIFIED_TIME;
return $this;
}
/**
* Filters the iterator with an anonymous function.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @param \Closure(SplFileInfo): bool $closure
* @param bool $prune Whether to skip traversing directories further
*
* @return $this
*
* @see CustomFilterIterator
*/
public function filter(\Closure $closure, bool $prune = false): static
{
$this->filters[] = $closure;
if ($prune) {
$this->pruneFilters[] = $closure;
}
return $this;
}
/**
* Forces the following of symlinks.
*
* @return $this
*/
public function followLinks(): static
{
$this->followLinks = true;
return $this;
}
/**
* Force the use of UNIX paths when recursing directories.
*
* @return $this
*/
public function useUnixPaths(): static
{
$this->unixPaths = true;
return $this;
}
/**
* Tells finder to ignore unreadable directories.
*
* By default, scanning unreadable directories content throws an AccessDeniedException.
*
* @return $this
*/
public function ignoreUnreadableDirs(bool $ignore = true): static
{
$this->ignoreUnreadableDirs = $ignore;
return $this;
}
/**
* Searches files and directories which match defined rules.
*
* @param string|string[] $dirs A directory path or an array of directories
*
* @return $this
*
* @throws DirectoryNotFoundException if one of the directories does not exist
*/
public function in(string|array $dirs): static
{
$resolvedDirs = [];
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = [$this->normalizeDir($dir)];
} elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
sort($glob);
$resolvedDirs[] = array_map($this->normalizeDir(...), $glob);
} else {
throw new DirectoryNotFoundException(\sprintf('The "%s" directory does not exist.', $dir));
}
}
$this->dirs = array_merge($this->dirs, ...$resolvedDirs);
return $this;
}
/**
* Returns an Iterator for the current Finder configuration.
*
* This method implements the IteratorAggregate interface.
*
* @return \Iterator<non-empty-string, SplFileInfo>
*
* @throws \LogicException if the in() method has not been called
*/
public function getIterator(): \Iterator
{
if (!$this->dirs && !$this->iterators) {
throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === \count($this->dirs) && !$this->iterators) {
$iterator = $this->searchInDirectory($this->dirs[0]);
} else {
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir))));
}
foreach ($this->iterators as $it) {
$iterator->append(new \IteratorIterator(new LazyIterator(static function () use ($it) {
foreach ($it as $file) {
if (!$file instanceof \SplFileInfo) {
$file = new \SplFileInfo($file);
}
$key = $file->getPathname();
if (!$file instanceof SplFileInfo) {
$file = new SplFileInfo($key, $file->getPath(), $key);
}
yield $key => $file;
}
})));
}
}
if ($this->sort || $this->reverseSorting) {
$iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
}
return $iterator;
}
/**
* Appends an existing set of files/directories to the finder.
*
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @param iterable<SplFileInfo|\SplFileInfo|string> $iterator
*
* @return $this
*/
public function append(iterable $iterator): static
{
$this->iterators[] = $iterator;
return $this;
}
/**
* Check if any results were found.
*/
public function hasResults(): bool
{
foreach ($this->getIterator() as $_) {
return true;
}
return false;
}
/**
* Counts all the results collected by the iterators.
*/
public function count(): int
{
return iterator_count($this->getIterator());
}
private function searchInDirectory(string $dir): \Iterator
{
$exclude = $this->exclude;
$notPaths = $this->notPaths;
if ($this->pruneFilters) {
$exclude = array_merge($exclude, $this->pruneFilters);
}
if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
$exclude = array_merge($exclude, self::$vcsPatterns);
}
if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
$notPaths[] = '#(^|/)\..+(/|$)#';
}
$minDepth = 0;
$maxDepth = \PHP_INT_MAX;
foreach ($this->depths as $comparator) {
switch ($comparator->getOperator()) {
case '>':
$minDepth = $comparator->getTarget() + 1;
break;
case '>=':
$minDepth = $comparator->getTarget();
break;
case '<':
$maxDepth = $comparator->getTarget() - 1;
break;
case '<=':
$maxDepth = $comparator->getTarget();
break;
default:
$minDepth = $maxDepth = $comparator->getTarget();
}
}
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
if ($this->followLinks) {
$flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
}
if ($this->unixPaths) {
$flags |= \RecursiveDirectoryIterator::UNIX_PATHS;
}
$iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
if ($exclude) {
$iterator = new ExcludeDirectoryFilterIterator($iterator, $exclude);
}
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
$iterator = new DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
}
if ($this->mode) {
$iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
}
if ($this->names || $this->notNames) {
$iterator = new FilenameFilterIterator($iterator, $this->names, $this->notNames);
}
if ($this->contains || $this->notContains) {
$iterator = new FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
}
if ($this->sizes) {
$iterator = new SizeRangeFilterIterator($iterator, $this->sizes);
}
if ($this->dates) {
$iterator = new DateRangeFilterIterator($iterator, $this->dates);
}
if ($this->filters) {
$iterator = new CustomFilterIterator($iterator, $this->filters);
}
if ($this->paths || $notPaths) {
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
}
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
$iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
}
return $iterator;
}
/**
* Normalizes given directory names by removing trailing slashes.
*
* Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
*/
private function normalizeDir(string $dir): string
{
if ('/' === $dir) {
return $dir;
}
$dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
$dir .= '/';
}
return $dir;
}
}
================================================
FILE: Gitignore.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Gitignore matches against text.
*
* @author Michael Voříšek <vorismi3@fel.cvut.cz>
* @author Ahmed Abdou <mail@ahmd.io>
*/
class Gitignore
{
/**
* Returns a regexp which is the equivalent of the gitignore pattern.
*
* Format specification: https://git-scm.com/docs/gitignore#_pattern_format
*/
public static function toRegex(string $gitignoreFileContent): string
{
return self::buildRegex($gitignoreFileContent, false);
}
public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string
{
return self::buildRegex($gitignoreFileContent, true);
}
private static function buildRegex(string $gitignoreFileContent, bool $inverted): string
{
$gitignoreFileContent = preg_replace('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
$gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);
$res = self::lineToRegex('');
foreach ($gitignoreLines as $line) {
$line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);
if (str_starts_with($line, '!')) {
$line = substr($line, 1);
$isNegative = true;
} else {
$isNegative = false;
}
if ('' !== $line) {
if ($isNegative xor $inverted) {
$res = '(?!'.self::lineToRegex($line).'$)'.$res;
} else {
$res = '(?:'.$res.'|'.self::lineToRegex($line).')';
}
}
}
return '~^(?:'.$res.')~s';
}
private static function lineToRegex(string $gitignoreLine): string
{
if ('' === $gitignoreLine) {
return '$f'; // always false
}
$slashPos = strpos($gitignoreLine, '/');
if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
if (0 === $slashPos) {
$gitignoreLine = substr($gitignoreLine, 1);
}
$isAbsolute = true;
} else {
$isAbsolute = false;
}
$regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
$regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', static fn (array $matches): string => '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex);
$regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
$regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
$regex = preg_replace('~\\\\\?~', '[^/]', $regex);
return ($isAbsolute ? '' : '(?:[^/]+/)*')
.$regex
.(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : '');
}
}
================================================
FILE: Glob.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Glob matches globbing patterns against text.
*
* if match_glob("foo.*", "foo.bar") echo "matched\n";
*
* // prints foo.bar and foo.baz
* $regex = glob_to_regex("foo.*");
* for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
* {
* if (/$regex/) echo "matched: $car\n";
* }
*
* Glob implements glob(3) style matching that can be used to match
* against text, rather than fetching names from a filesystem.
*
* Based on the Perl Text::Glob module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*/
class Glob
{
/**
* Returns a regexp which is the equivalent of the glob pattern.
*/
public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#'): string
{
$firstByte = true;
$escaping = false;
$inCurlies = 0;
$regex = '';
if ($unanchored = str_starts_with($glob, '**/')) {
$glob = '/'.$glob;
}
$sizeGlob = \strlen($glob);
for ($i = 0; $i < $sizeGlob; ++$i) {
$car = $glob[$i];
if ($firstByte && $strictLeadingDot && '.' !== $car) {
$regex .= '(?=[^\.])';
}
$firstByte = '/' === $car;
if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
$car = '[^/]++/';
if (!isset($glob[$i + 3])) {
$car .= '?';
}
if ($strictLeadingDot) {
$car = '(?=[^\.])'.$car;
}
$car = '/(?:'.$car.')*';
$i += 2 + isset($glob[$i + 3]);
if ('/' === $delimiter) {
$car = str_replace('/', '\\/', $car);
}
}
if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
$regex .= "\\$car";
} elseif ('*' === $car) {
$regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
} elseif ('?' === $car) {
$regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
} elseif ('{' === $car) {
$regex .= $escaping ? '\\{' : '(';
if (!$escaping) {
++$inCurlies;
}
} elseif ('}' === $car && $inCurlies) {
$regex .= $escaping ? '}' : ')';
if (!$escaping) {
--$inCurlies;
}
} elseif (',' === $car && $inCurlies) {
$regex .= $escaping ? ',' : '|';
} elseif ('\\' === $car) {
if ($escaping) {
$regex .= '\\\\';
$escaping = false;
} else {
$escaping = true;
}
continue;
} else {
$regex .= $car;
}
$escaping = false;
}
if ($unanchored) {
$regex = substr_replace($regex, '?', 1 + ('/' === $delimiter) + ($strictLeadingDot ? \strlen('(?=[^\.])') : 0), 0);
}
return $delimiter.'^'.$regex.'$'.$delimiter;
}
}
================================================
FILE: Iterator/CustomFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* CustomFilterIterator filters files by applying anonymous functions.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class CustomFilterIterator extends \FilterIterator
{
private array $filters = [];
/**
* @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
*
* @throws \InvalidArgumentException
*/
public function __construct(\Iterator $iterator, array $filters)
{
foreach ($filters as $filter) {
if (!\is_callable($filter)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
}
$this->filters = $filters;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*/
public function accept(): bool
{
$fileinfo = $this->current();
foreach ($this->filters as $filter) {
if (false === $filter($fileinfo)) {
return false;
}
}
return true;
}
}
================================================
FILE: Iterator/DateRangeFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\DateComparator;
/**
* DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class DateRangeFilterIterator extends \FilterIterator
{
private array $comparators = [];
/**
* @param \Iterator<string, \SplFileInfo> $iterator
* @param DateComparator[] $comparators
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*/
public function accept(): bool
{
$fileinfo = $this->current();
if (!file_exists($fileinfo->getPathname())) {
return false;
}
$filedate = $fileinfo->getMTime();
foreach ($this->comparators as $compare) {
if (!$compare->test($filedate)) {
return false;
}
}
return true;
}
}
================================================
FILE: Iterator/DepthRangeFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* DepthRangeFilterIterator limits the directory depth.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @template-covariant TKey
* @template-covariant TValue
*
* @extends \FilterIterator<TKey, TValue>
*/
class DepthRangeFilterIterator extends \FilterIterator
{
private int $minDepth = 0;
/**
* @param \RecursiveIteratorIterator<\RecursiveIterator<TKey, TValue>> $iterator The Iterator to filter
* @param int $minDepth The min depth
* @param int $maxDepth The max depth
*/
public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
{
$this->minDepth = $minDepth;
$iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*/
public function accept(): bool
{
return $this->getInnerIterator()->getDepth() >= $this->minDepth;
}
}
================================================
FILE: Iterator/ExcludeDirectoryFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\SplFileInfo;
/**
* ExcludeDirectoryFilterIterator filters out directories.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, SplFileInfo>
*
* @implements \RecursiveIterator<string, SplFileInfo>
*/
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
/** @var \Iterator<string, SplFileInfo> */
private \Iterator $iterator;
private bool $isRecursive;
/** @var array<string, true> */
private array $excludedDirs = [];
private ?string $excludedPattern = null;
/** @var list<callable(SplFileInfo):bool> */
private array $pruneFilters = [];
/**
* @param \Iterator<string, SplFileInfo> $iterator The Iterator to filter
* @param list<string|callable(SplFileInfo):bool> $directories An array of directories to exclude
*/
public function __construct(\Iterator $iterator, array $directories)
{
$this->iterator = $iterator;
$this->isRecursive = $iterator instanceof \RecursiveIterator;
$patterns = [];
foreach ($directories as $directory) {
if (!\is_string($directory)) {
if (!\is_callable($directory)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
$this->pruneFilters[] = $directory;
continue;
}
$directory = rtrim($directory, '/');
if (!$this->isRecursive || str_contains($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
}
}
if ($patterns) {
$this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
}
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*/
public function accept(): bool
{
if ($this->isRecursive && isset($this->excludedDirs[$this->current()->getFilename()]) && $this->current()->isDir()) {
return false;
}
if ($this->excludedPattern) {
$path = $this->current()->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
$path = str_replace('\\', '/', $path);
return !preg_match($this->excludedPattern, $path);
}
if ($this->pruneFilters && $this->hasChildren()) {
foreach ($this->pruneFilters as $pruneFilter) {
if (!$pruneFilter($this->current())) {
return false;
}
}
}
return true;
}
public function hasChildren(): bool
{
return $this->isRecursive && $this->iterator->hasChildren();
}
public function getChildren(): self
{
$children = new self($this->iterator->getChildren(), []);
$children->excludedDirs = $this->excludedDirs;
$children->excludedPattern = $this->excludedPattern;
return $children;
}
}
================================================
FILE: Iterator/FileTypeFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FileTypeFilterIterator only keeps files, directories, or both.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class FileTypeFilterIterator extends \FilterIterator
{
public const ONLY_FILES = 1;
public const ONLY_DIRECTORIES = 2;
/**
* @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
* @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
*/
public function __construct(
\Iterator $iterator,
private int $mode,
) {
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*/
public function accept(): bool
{
$fileinfo = $this->current();
if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
return false;
} elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
return false;
}
return true;
}
}
================================================
FILE: Iterator/FilecontentFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\SplFileInfo;
/**
* FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
*
* @extends MultiplePcreFilterIterator<string, SplFileInfo>
*/
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*/
public function accept(): bool
{
if (!$this->matchRegexps && !$this->noMatchRegexps) {
return true;
}
$fileinfo = $this->current();
if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
return false;
}
$content = $fileinfo->getContents();
if (!$content) {
return false;
}
return $this->isAccepted($content);
}
/**
* Converts string to regexp if necessary.
*
* @param string $str Pattern: string or regexp
*/
protected function toRegex(string $str): string
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
================================================
FILE: Iterator/FilenameFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Glob;
/**
* FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends MultiplePcreFilterIterator<string, \SplFileInfo>
*/
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*/
public function accept(): bool
{
return $this->isAccepted($this->current()->getFilename());
}
/**
* Converts glob to regexp.
*
* PCRE patterns are left unchanged.
* Glob strings are transformed with Glob::toRegex().
*
* @param string $str Pattern: glob or regexp
*/
protected function toRegex(string $str): string
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
}
================================================
FILE: Iterator/LazyIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*
* @internal
*/
class LazyIterator implements \IteratorAggregate
{
private \Closure $iteratorFactory;
public function __construct(callable $iteratorFactory)
{
$this->iteratorFactory = $iteratorFactory(...);
}
public function getIterator(): \Traversable
{
yield from ($this->iteratorFactory)();
}
}
================================================
FILE: Iterator/MultiplePcreFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @template-covariant TKey
* @template-covariant TValue
*
* @extends \FilterIterator<TKey, TValue>
*/
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
protected array $matchRegexps = [];
protected array $noMatchRegexps = [];
/**
* @param \Iterator<TKey, TValue> $iterator The Iterator to filter
* @param string[] $matchPatterns An array of patterns that need to match
* @param string[] $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Checks whether the string is accepted by the regex filters.
*
* If there is no regexps defined in the class, this method will accept the string.
* Such case can be handled by child classes before calling the method if they want to
* apply a different behavior.
*/
protected function isAccepted(string $string): bool
{
// should at least not match one rule to exclude
foreach ($this->noMatchRegexps as $regex) {
if (preg_match($regex, $string)) {
return false;
}
}
// should at least match one rule
if ($this->matchRegexps) {
foreach ($this->matchRegexps as $regex) {
if (preg_match($regex, $string)) {
return true;
}
}
return false;
}
// If there is no match rules, the file is accepted
return true;
}
/**
* Checks whether the string is a regex.
*/
protected function isRegex(string $str): bool
{
$availableModifiers = 'imsxuADUn';
if (preg_match('/^(.{3,}?)['.$availableModifiers.']*$/', $str, $m)) {
$start = substr($m[1], 0, 1);
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
/**
* Converts string into regexp.
*/
abstract protected function toRegex(string $str): string;
}
================================================
FILE: Iterator/PathFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\SplFileInfo;
/**
* PathFilterIterator filters files by path patterns (e.g. some/special/dir).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
*
* @extends MultiplePcreFilterIterator<string, SplFileInfo>
*/
class PathFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*/
public function accept(): bool
{
$filename = $this->current()->getRelativePathname();
if ('\\' === \DIRECTORY_SEPARATOR) {
$filename = str_replace('\\', '/', $filename);
}
return $this->isAccepted($filename);
}
/**
* Converts strings to regexp.
*
* PCRE patterns are left unchanged.
*
* Default conversion:
* 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/'
*
* Use only / as directory separator (on Windows also).
*
* @param string $str Pattern: regexp or dirname
*/
protected function toRegex(string $str): string
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
================================================
FILE: Iterator/RecursiveDirectoryIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;
/**
* Extends the \RecursiveDirectoryIterator to support relative paths.
*
* @author Victor Berchet <victor@suumit.com>
*
* @extends \RecursiveDirectoryIterator<string, SplFileInfo>
*/
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
private bool $ignoreUnreadableDirs;
private bool $ignoreFirstRewind = true;
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
private string $rootPath;
private string $subPath;
private string $directorySeparator = '/';
/**
* @throws \RuntimeException
*/
public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
{
if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
throw new \RuntimeException('This iterator only support returning current as fileinfo.');
}
parent::__construct($path, $flags);
$this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
$this->rootPath = $path;
if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
$this->directorySeparator = \DIRECTORY_SEPARATOR;
}
}
/**
* Return an instance of SplFileInfo with support for relative paths.
*/
public function current(): SplFileInfo
{
// the logic here avoids redoing the same work in all iterations
if (!isset($this->subPath)) {
$this->subPath = $this->getSubPath();
}
$subPathname = $this->subPath;
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
}
$subPathname .= $this->getFilename();
$basePath = $this->rootPath;
if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) {
$basePath .= $this->directorySeparator;
}
return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
}
public function hasChildren(bool $allowLinks = false): bool
{
$hasChildren = parent::hasChildren($allowLinks);
if (!$hasChildren || !$this->ignoreUnreadableDirs) {
return $hasChildren;
}
try {
parent::getChildren();
return true;
} catch (\UnexpectedValueException) {
// If directory is unreadable and finder is set to ignore it, skip children
return false;
}
}
/**
* @throws AccessDeniedException
*/
public function getChildren(): \RecursiveDirectoryIterator
{
try {
$children = parent::getChildren();
if ($children instanceof self) {
// parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
$children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
// performance optimization to avoid redoing the same work in all children
$children->rootPath = $this->rootPath;
}
return $children;
} catch (\UnexpectedValueException $e) {
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
}
public function next(): void
{
$this->ignoreFirstRewind = false;
parent::next();
}
public function rewind(): void
{
// some streams like FTP are not rewindable, ignore the first rewind after creation,
// as newly created DirectoryIterator does not need to be rewound
if ($this->ignoreFirstRewind) {
$this->ignoreFirstRewind = false;
return;
}
parent::rewind();
}
}
================================================
FILE: Iterator/SizeRangeFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\NumberComparator;
/**
* SizeRangeFilterIterator filters out files that are not in the given size range.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class SizeRangeFilterIterator extends \FilterIterator
{
private array $comparators = [];
/**
* @param \Iterator<string, \SplFileInfo> $iterator
* @param NumberComparator[] $comparators
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*/
public function accept(): bool
{
$fileinfo = $this->current();
if (!$fileinfo->isFile()) {
return true;
}
$filesize = $fileinfo->getSize();
foreach ($this->comparators as $compare) {
if (!$compare->test($filesize)) {
return false;
}
}
return true;
}
}
================================================
FILE: Iterator/SortableIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* SortableIterator applies a sort on a given Iterator.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<string, \SplFileInfo>
*/
class SortableIterator implements \IteratorAggregate
{
public const SORT_BY_NONE = 0;
public const SORT_BY_NAME = 1;
public const SORT_BY_TYPE = 2;
public const SORT_BY_ACCESSED_TIME = 3;
public const SORT_BY_CHANGED_TIME = 4;
public const SORT_BY_MODIFIED_TIME = 5;
public const SORT_BY_NAME_NATURAL = 6;
public const SORT_BY_NAME_CASE_INSENSITIVE = 7;
public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8;
public const SORT_BY_EXTENSION = 9;
public const SORT_BY_SIZE = 10;
/** @var \Traversable<string, \SplFileInfo> */
private \Traversable $iterator;
private \Closure|int $sort;
/**
* @param \Traversable<string, \SplFileInfo> $iterator
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
*
* @throws \InvalidArgumentException
*/
public function __construct(\Traversable $iterator, int|callable $sort, bool $reverseOrder = false)
{
$this->iterator = $iterator;
$order = $reverseOrder ? -1 : 1;
if (self::SORT_BY_NAME === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
} elseif (self::SORT_BY_NAME_NATURAL === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
} elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
} elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
} elseif (self::SORT_BY_TYPE === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
if ($a->isDir() && $b->isFile()) {
return -$order;
} elseif ($a->isFile() && $b->isDir()) {
return $order;
}
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime());
} elseif (self::SORT_BY_CHANGED_TIME === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime());
} elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime());
} elseif (self::SORT_BY_EXTENSION === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension());
} elseif (self::SORT_BY_SIZE === $sort) {
$this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize());
} elseif (self::SORT_BY_NONE === $sort) {
$this->sort = $order;
} elseif (\is_callable($sort)) {
$this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...);
} else {
throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
}
}
public function getIterator(): \Traversable
{
if (1 === $this->sort) {
yield from $this->iterator;
return;
}
$keys = $values = [];
foreach ($this->iterator as $key => $value) {
$keys[] = $key;
$values[] = $value;
}
if (-1 === $this->sort) {
for ($i = \count($values) - 1; $i >= 0; --$i) {
yield $keys[$i] => $values[$i];
}
return;
}
uasort($values, $this->sort);
foreach ($values as $i => $v) {
yield $keys[$i] => $v;
}
}
}
================================================
FILE: Iterator/VcsIgnoredFilterIterator.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Gitignore;
/**
* @extends \FilterIterator<string, \SplFileInfo>
*/
final class VcsIgnoredFilterIterator extends \FilterIterator
{
private string $baseDir;
/**
* @var array<string, array{0: string, 1: string}|null>
*/
private array $gitignoreFilesCache = [];
/**
* @var array<string, bool>
*/
private array $ignoredPathsCache = [];
/**
* @param \Iterator<string, \SplFileInfo> $iterator
*/
public function __construct(\Iterator $iterator, string $baseDir)
{
$this->baseDir = $this->normalizePath($baseDir);
foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) {
if (@is_dir("{$directory}/.git")) {
$this->baseDir = $directory;
break;
}
}
parent::__construct($iterator);
}
public function accept(): bool
{
$file = $this->current();
$fileRealPath = $this->normalizePath($file->getRealPath());
return !$this->isIgnored($fileRealPath);
}
private function isIgnored(string $fileRealPath): bool
{
if (is_dir($fileRealPath) && !str_ends_with($fileRealPath, '/')) {
$fileRealPath .= '/';
}
if (isset($this->ignoredPathsCache[$fileRealPath])) {
return $this->ignoredPathsCache[$fileRealPath];
}
$ignored = false;
foreach ($this->parentDirectoriesDownwards($fileRealPath) as $parentDirectory) {
if ($this->isIgnored($parentDirectory)) {
// rules in ignored directories are ignored, no need to check further.
break;
}
$fileRelativePath = substr($fileRealPath, \strlen($parentDirectory) + 1);
if (null === $regexps = $this->readGitignoreFile("{$parentDirectory}/.gitignore")) {
continue;
}
[$exclusionRegex, $inclusionRegex] = $regexps;
if (preg_match($exclusionRegex, $fileRelativePath)) {
$ignored = true;
continue;
}
if (preg_match($inclusionRegex, $fileRelativePath)) {
$ignored = false;
}
}
return $this->ignoredPathsCache[$fileRealPath] = $ignored;
}
/**
* @return list<string>
*/
private function parentDirectoriesUpwards(string $from): array
{
$parentDirectories = [];
$parentDirectory = $from;
while (true) {
$newParentDirectory = \dirname($parentDirectory);
// dirname('/') = '/'
if ($newParentDirectory === $parentDirectory) {
break;
}
$parentDirectories[] = $parentDirectory = $newParentDirectory;
}
return $parentDirectories;
}
private function parentDirectoriesUpTo(string $from, string $upTo): array
{
return array_filter(
$this->parentDirectoriesUpwards($from),
static fn (string $directory): bool => str_starts_with($directory, $upTo)
);
}
/**
* @return list<string>
*/
private function parentDirectoriesDownwards(string $fileRealPath): array
{
return array_reverse(
$this->parentDirectoriesUpTo($fileRealPath, $this->baseDir)
);
}
/**
* @return array{0: string, 1: string}|null
*/
private function readGitignoreFile(string $path): ?array
{
if (\array_key_exists($path, $this->gitignoreFilesCache)) {
return $this->gitignoreFilesCache[$path];
}
if (!file_exists($path)) {
return $this->gitignoreFilesCache[$path] = null;
}
if (!is_file($path) || !is_readable($path)) {
throw new \RuntimeException("The \"ignoreVCSIgnored\" option cannot be used by the Finder as the \"{$path}\" file is not readable.");
}
$gitignoreFileContent = file_get_contents($path);
return $this->gitignoreFilesCache[$path] = [
Gitignore::toRegex($gitignoreFileContent),
Gitignore::toRegexMatchingNegatedPatterns($gitignoreFileContent),
];
}
private function normalizePath(string $path): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
return str_replace('\\', '/', $path);
}
return $path;
}
}
================================================
FILE: LICENSE
================================================
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
Finder Component
================
The Finder component finds files and directories via an intuitive fluent
interface.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/finder.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
================================================
FILE: SplFileInfo.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Extends \SplFileInfo to support relative paths.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SplFileInfo extends \SplFileInfo
{
/**
* @param string $file The file name
* @param string $relativePath The relative path
* @param string $relativePathname The relative path name
*/
public function __construct(
string $file,
private string $relativePath,
private string $relativePathname,
) {
parent::__construct($file);
}
/**
* Returns the relative path.
*
* This path does not contain the file name.
*/
public function getRelativePath(): string
{
return $this->relativePath;
}
/**
* Returns the relative path name.
*
* This path contains the file name.
*/
public function getRelativePathname(): string
{
return $this->relativePathname;
}
public function getFilenameWithoutExtension(): string
{
$filename = $this->getFilename();
return pathinfo($filename, \PATHINFO_FILENAME);
}
/**
* Returns the contents of the file.
*
* @throws \RuntimeException
*/
public function getContents(): string
{
set_error_handler(static function ($type, $msg) use (&$error) { $error = $msg; });
try {
$content = file_get_contents($this->getPathname());
} finally {
restore_error_handler();
}
if (false === $content) {
throw new \RuntimeException($error);
}
return $content;
}
}
================================================
FILE: Tests/Comparator/ComparatorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Comparator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Finder\Comparator\Comparator;
class ComparatorTest extends TestCase
{
public function testInvalidOperator()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid operator "foo".');
new Comparator('some target', 'foo');
}
#[DataProvider('provideMatches')]
public function testTestSucceeds(string $operator, string $target, string $testedValue)
{
$c = new Comparator($target, $operator);
$this->assertSame($target, $c->getTarget());
$this->assertSame($operator, $c->getOperator());
$this->assertTrue($c->test($testedValue));
}
public static function provideMatches(): array
{
return [
['<', '1000', '500'],
['<', '1000', '999'],
['<=', '1000', '999'],
['!=', '1000', '999'],
['<=', '1000', '1000'],
['==', '1000', '1000'],
['>=', '1000', '1000'],
['>=', '1000', '1001'],
['>', '1000', '1001'],
['>', '1000', '5000'],
];
}
#[DataProvider('provideNonMatches')]
public function testTestFails(string $operator, string $target, string $testedValue)
{
$c = new Comparator($target, $operator);
$this->assertFalse($c->test($testedValue));
}
public static function provideNonMatches(): array
{
return [
['>', '1000', '500'],
['>=', '1000', '500'],
['>', '1000', '1000'],
['!=', '1000', '1000'],
['<', '1000', '1000'],
['<', '1000', '1500'],
['<=', '1000', '1500'],
];
}
}
================================================
FILE: Tests/Comparator/DateComparatorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Comparator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Finder\Comparator\DateComparator;
class DateComparatorTest extends TestCase
{
public function testConstructor()
{
try {
new DateComparator('foobar');
$this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
} catch (\Exception $e) {
$this->assertInstanceOf(\InvalidArgumentException::class, $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
}
try {
new DateComparator('');
$this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
} catch (\Exception $e) {
$this->assertInstanceOf(\InvalidArgumentException::class, $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
}
}
#[DataProvider('getTestData')]
public function testTest($test, $match, $noMatch)
{
$c = new DateComparator($test);
foreach ($match as $m) {
$this->assertTrue($c->test($m), '->test() tests a string against the expression');
}
foreach ($noMatch as $m) {
$this->assertFalse($c->test($m), '->test() tests a string against the expression');
}
}
public static function getTestData()
{
return [
['< 2005-10-10', [strtotime('2005-10-09')], [strtotime('2005-10-15')]],
['until 2005-10-10', [strtotime('2005-10-09')], [strtotime('2005-10-15')]],
['before 2005-10-10', [strtotime('2005-10-09')], [strtotime('2005-10-15')]],
['> 2005-10-10', [strtotime('2005-10-15')], [strtotime('2005-10-09')]],
['after 2005-10-10', [strtotime('2005-10-15')], [strtotime('2005-10-09')]],
['since 2005-10-10', [strtotime('2005-10-15')], [strtotime('2005-10-09')]],
['!= 2005-10-10', [strtotime('2005-10-11')], [strtotime('2005-10-10')]],
['2005-10-10', [strtotime('2005-10-10')], [strtotime('2005-10-11')]],
];
}
}
================================================
FILE: Tests/Comparator/NumberComparatorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Comparator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Finder\Comparator\NumberComparator;
class NumberComparatorTest extends TestCase
{
#[DataProvider('getConstructorTestData')]
public function testConstructor($successes, $failures)
{
foreach ($successes as $s) {
new NumberComparator($s);
}
foreach ($failures as $f) {
try {
new NumberComparator($f);
$this->fail('__construct() throws an \InvalidArgumentException if the test expression is not valid.');
} catch (\Exception $e) {
$this->assertInstanceOf(\InvalidArgumentException::class, $e, '__construct() throws an \InvalidArgumentException if the test expression is not valid.');
}
}
}
#[DataProvider('getTestData')]
public function testTest($test, $match, $noMatch)
{
$c = new NumberComparator($test);
foreach ($match as $m) {
$this->assertTrue($c->test($m), '->test() tests a string against the expression');
}
foreach ($noMatch as $m) {
$this->assertFalse($c->test($m), '->test() tests a string against the expression');
}
}
public static function getTestData()
{
return [
['< 1000', ['500', '999'], ['1000', '1500']],
['< 1K', ['500', '999'], ['1000', '1500']],
['<1k', ['500', '999'], ['1000', '1500']],
[' < 1 K ', ['500', '999'], ['1000', '1500']],
['<= 1K', ['1000'], ['1001']],
['> 1K', ['1001'], ['1000']],
['>= 1K', ['1000'], ['999']],
['< 1KI', ['500', '1023'], ['1024', '1500']],
['<= 1KI', ['1024'], ['1025']],
['> 1KI', ['1025'], ['1024']],
['>= 1KI', ['1024'], ['1023']],
['1KI', ['1024'], ['1023', '1025']],
['==1KI', ['1024'], ['1023', '1025']],
['==1m', ['1000000'], ['999999', '1000001']],
['==1mi', [1024 * 1024], [1024 * 1024 - 1, 1024 * 1024 + 1]],
['==1g', ['1000000000'], ['999999999', '1000000001']],
['==1gi', [1024 * 1024 * 1024], [1024 * 1024 * 1024 - 1, 1024 * 1024 * 1024 + 1]],
['!= 1000', ['500', '999'], ['1000']],
];
}
public static function getConstructorTestData()
{
return [
[
[
'1', '0',
'3.5', '33.55', '123.456', '123456.78',
'.1', '.123',
'.0', '0.0',
'1.', '0.', '123.',
'==1', '!=1', '<1', '>1', '<=1', '>=1',
'==1k', '==1ki', '==1m', '==1mi', '==1g', '==1gi',
'1k', '1ki', '1m', '1mi', '1g', '1gi',
],
[
null, '',
' ', 'foobar',
'=1', '===1',
'0 . 1', '123 .45', '234. 567',
'..', '.0.', '0.1.2',
],
],
];
}
}
================================================
FILE: Tests/FinderOpenBasedirTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests;
use PHPUnit\Framework\Attributes\RunInSeparateProcess;
use Symfony\Component\Finder\Finder;
class FinderOpenBasedirTest extends Iterator\RealIteratorTestCase
{
#[RunInSeparateProcess]
public function testIgnoreVCSIgnoredWithOpenBasedir()
{
$this->markTestIncomplete('Test case needs to be refactored so that PHPUnit can run it');
if (\ini_get('open_basedir')) {
$this->markTestSkipped('Cannot test when open_basedir is set');
}
$finder = $this->buildFinder();
$this->assertSame(
$finder,
$finder
->ignoreVCS(true)
->ignoreDotFiles(true)
->ignoreVCSIgnored(true)
);
$openBaseDir = \dirname(__DIR__, 5).\PATH_SEPARATOR.self::toAbsolute('gitignore/search_root');
if ($deprecationsFile = getenv('SYMFONY_DEPRECATIONS_SERIALIZE')) {
$openBaseDir .= \PATH_SEPARATOR.$deprecationsFile;
}
$oldOpenBaseDir = ini_set('open_basedir', $openBaseDir);
try {
$this->assertIterator(self::toAbsolute([
'gitignore/search_root/b.txt',
'gitignore/search_root/c.txt',
'gitignore/search_root/dir',
'gitignore/search_root/dir/a.txt',
'gitignore/search_root/dir/c.txt',
]), $finder->in(self::toAbsolute('gitignore/search_root'))->getIterator());
} finally {
ini_set('open_basedir', $oldOpenBaseDir);
}
}
protected function buildFinder()
{
return Finder::create()->exclude('gitignore');
}
}
================================================
FILE: Tests/FinderTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\ExpectationFailedException;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
class FinderTest extends Iterator\RealIteratorTestCase
{
use Iterator\VfsIteratorTestTrait;
public function testCreate()
{
$this->assertInstanceOf(Finder::class, Finder::create());
}
public function testDirectories()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->directories());
$this->assertIterator($this->toAbsolute(['foo', 'qux', 'toto']), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->directories();
$finder->files();
$finder->directories();
$this->assertIterator($this->toAbsolute(['foo', 'qux', 'toto']), $finder->in(self::$tmpDir)->getIterator());
}
public function testFiles()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->files());
$this->assertIterator($this->toAbsolute(['foo/bar.tmp',
'test.php',
'test.py',
'foo bar',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->files();
$finder->directories();
$finder->files();
$this->assertIterator($this->toAbsolute(['foo/bar.tmp',
'test.php',
'test.py',
'foo bar',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testRemoveTrailingSlash()
{
$finder = $this->buildFinder();
$expected = $this->toAbsolute([
'foo/bar.tmp',
'test.php',
'test.py',
'foo bar',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
'Zephire.php',
]);
$in = self::$tmpDir.'//';
$this->assertIterator($expected, $finder->in($in)->files()->getIterator());
}
public function testSymlinksNotResolved()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('symlinks are not supported on Windows');
}
$finder = $this->buildFinder();
symlink($this->toAbsolute('foo'), $this->toAbsolute('baz'));
$expected = $this->toAbsolute(['baz/bar.tmp']);
$in = self::$tmpDir.'/baz/';
try {
$this->assertIterator($expected, $finder->in($in)->files()->getIterator());
unlink($this->toAbsolute('baz'));
} catch (\Exception $e) {
unlink($this->toAbsolute('baz'));
throw $e;
}
}
public function testBackPathNotNormalized()
{
$finder = $this->buildFinder();
$expected = $this->toAbsolute(['foo/../foo/bar.tmp']);
$in = self::$tmpDir.'/foo/../foo/';
$this->assertIterator($expected, $finder->in($in)->files()->getIterator());
}
public function testDepth()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->depth('< 1'));
$this->assertIterator($this->toAbsolute(['foo',
'test.php',
'test.py',
'toto',
'foo bar',
'qux',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->depth('<= 0'));
$this->assertIterator($this->toAbsolute(['foo',
'test.php',
'test.py',
'toto',
'foo bar',
'qux',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->depth('>= 1'));
$this->assertIterator($this->toAbsolute([
'foo/bar.tmp',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->depth('< 1')->depth('>= 1');
$this->assertIterator([], $finder->in(self::$tmpDir)->getIterator());
}
public function testDepthWithArrayParam()
{
$finder = $this->buildFinder();
$finder->depth(['>= 1', '< 2']);
$this->assertIterator($this->toAbsolute([
'foo/bar.tmp',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testName()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->name('*.php'));
$this->assertIterator($this->toAbsolute([
'test.php',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->name('test.ph*');
$finder->name('test.py');
$this->assertIterator($this->toAbsolute(['test.php', 'test.py']), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->name('~^test~i');
$this->assertIterator($this->toAbsolute(['test.php', 'test.py']), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->name('~\\.php$~i');
$this->assertIterator($this->toAbsolute([
'test.php',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->name('test.p{hp,y}');
$this->assertIterator($this->toAbsolute(['test.php', 'test.py']), $finder->in(self::$tmpDir)->getIterator());
}
public function testNameWithArrayParam()
{
$finder = $this->buildFinder();
$finder->name(['test.php', 'test.py']);
$this->assertIterator($this->toAbsolute(['test.php', 'test.py']), $finder->in(self::$tmpDir)->getIterator());
}
public function testNotName()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->notName('*.php'));
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'test.py',
'toto',
'foo bar',
'qux',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->notName('*.php');
$finder->notName('*.py');
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'toto',
'foo bar',
'qux',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->name('test.ph*');
$finder->name('test.py');
$finder->notName('*.php');
$finder->notName('*.py');
$this->assertIterator([], $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->name('test.ph*');
$finder->name('test.py');
$finder->notName('*.p{hp,y}');
$this->assertIterator([], $finder->in(self::$tmpDir)->getIterator());
}
public function testNotNameWithArrayParam()
{
$finder = $this->buildFinder();
$finder->notName(['*.php', '*.py']);
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'toto',
'foo bar',
'qux',
]), $finder->in(self::$tmpDir)->getIterator());
}
#[DataProvider('getRegexNameTestData')]
public function testRegexName($regex)
{
$finder = $this->buildFinder();
$finder->name($regex);
$this->assertIterator($this->toAbsolute([
'test.py',
'test.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSize()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->files()->size('< 1K')->size('> 500'));
$this->assertIterator($this->toAbsolute(['test.php']), $finder->in(self::$tmpDir)->getIterator());
}
public function testSizeWithArrayParam()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->files()->size(['< 1K', '> 500']));
$this->assertIterator($this->toAbsolute(['test.php']), $finder->in(self::$tmpDir)->getIterator());
}
public function testDate()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->files()->date('until last month'));
$this->assertIterator($this->toAbsolute(['foo/bar.tmp', 'test.php']), $finder->in(self::$tmpDir)->getIterator());
}
public function testDateWithArrayParam()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->files()->date(['>= 2005-10-15', 'until last month']));
$this->assertIterator($this->toAbsolute(['foo/bar.tmp', 'test.php']), $finder->in(self::$tmpDir)->getIterator());
}
public function testExclude()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->exclude('foo'));
$this->assertIterator($this->toAbsolute([
'test.php',
'test.py',
'toto',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testIgnoreVCS()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->ignoreVCS(false)->ignoreDotFiles(false));
$this->assertIterator($this->toAbsolute([
'.git',
'foo',
'foo/bar.tmp',
'test.php',
'test.py',
'toto',
'toto/.git',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->ignoreVCS(false)->ignoreVCS(false)->ignoreDotFiles(false);
$this->assertIterator($this->toAbsolute([
'.git',
'foo',
'foo/bar.tmp',
'test.php',
'test.py',
'toto',
'toto/.git',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->ignoreVCS(true)->ignoreDotFiles(false));
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'test.php',
'test.py',
'toto',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testIgnoreVCSIgnored()
{
$finder = $this->buildFinder();
$this->assertSame(
$finder,
$finder
->ignoreVCS(true)
->ignoreDotFiles(true)
->ignoreVCSIgnored(true)
);
$this->assertIterator(self::toAbsolute([
'gitignore/search_root/b.txt',
'gitignore/search_root/dir',
'gitignore/search_root/dir/a.txt',
]), $finder->in(self::toAbsolute('gitignore/search_root'))->getIterator());
}
public function testIgnoreVCSIgnoredUpToFirstGitRepositoryRoot()
{
$finder = $this->buildFinder();
$this->assertSame(
$finder,
$finder
->ignoreVCS(true)
->ignoreDotFiles(true)
->ignoreVCSIgnored(true)
);
$this->assertIterator(self::toAbsolute([
'gitignore/git_root/search_root/b.txt',
'gitignore/git_root/search_root/c.txt',
'gitignore/git_root/search_root/dir',
'gitignore/git_root/search_root/dir/a.txt',
'gitignore/git_root/search_root/dir/c.txt',
]), $finder->in(self::toAbsolute('gitignore/git_root/search_root'))->getIterator());
}
public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
{
$finder = $this->buildFinder();
$finder->in(self::$tmpDir);
$finder->ignoreDotFiles(false);
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo bar',
]), $finder->getIterator());
$finder->ignoreVCS(false);
$this->assertIterator($this->toAbsolute([
'.git',
'foo',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'toto/.git',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo bar',
]), $finder->getIterator());
}
public function testIgnoreDotFiles()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->ignoreDotFiles(false)->ignoreVCS(false));
$this->assertIterator($this->toAbsolute([
'.git',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo',
'foo/bar.tmp',
'test.php',
'test.py',
'toto',
'toto/.git',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$finder->ignoreDotFiles(false)->ignoreDotFiles(false)->ignoreVCS(false);
$this->assertIterator($this->toAbsolute([
'.git',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo',
'foo/bar.tmp',
'test.php',
'test.py',
'toto',
'toto/.git',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->ignoreDotFiles(true)->ignoreVCS(false));
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'test.php',
'test.py',
'toto',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testIgnoreDotFilesCanBeDisabledAfterFirstIteration()
{
$finder = $this->buildFinder();
$finder->in(self::$tmpDir);
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'foo bar',
]), $finder->getIterator());
$finder->ignoreDotFiles(false);
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'foo bar',
]), $finder->getIterator());
}
public function testSortByName()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByName());
$this->assertOrderedIterator($this->toAbsolute([
'Zephire.php',
'foo',
'foo bar',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'zebulon.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSortByType()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByType());
$this->assertOrderedIterator($this->toAbsolute([
'foo',
'qux',
'toto',
'Zephire.php',
'foo bar',
'foo/bar.tmp',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'zebulon.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSortByAccessedTime()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByAccessedTime());
$this->assertIterator($this->toAbsolute([
'foo/bar.tmp',
'test.php',
'toto',
'test.py',
'foo',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSortByChangedTime()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByChangedTime());
$this->assertIterator($this->toAbsolute([
'toto',
'test.py',
'test.php',
'foo/bar.tmp',
'foo',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSortByModifiedTime()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByModifiedTime());
$this->assertIterator($this->toAbsolute([
'foo/bar.tmp',
'test.php',
'toto',
'test.py',
'foo',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testReverseSorting()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByName());
$this->assertSame($finder, $finder->reverseSorting());
$this->assertOrderedIteratorInForeach($this->toAbsolute([
'zebulon.php',
'toto',
'test.py',
'test.php',
'qux_2_0.php',
'qux_12_0.php',
'qux_10_2.php',
'qux_1002_0.php',
'qux_1000_1.php',
'qux_0_1.php',
'qux/baz_1_2.py',
'qux/baz_100_1.py',
'qux',
'foo/bar.tmp',
'foo bar',
'foo',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSortByNameNatural()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByName(true));
$this->assertOrderedIterator($this->toAbsolute([
'Zephire.php',
'foo',
'foo/bar.tmp',
'foo bar',
'qux',
'qux/baz_1_2.py',
'qux/baz_100_1.py',
'qux_0_1.php',
'qux_2_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_1000_1.php',
'qux_1002_0.php',
'test.php',
'test.py',
'toto',
'zebulon.php',
]), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByName(false));
$this->assertOrderedIterator($this->toAbsolute([
'Zephire.php',
'foo',
'foo bar',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'zebulon.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSortByNameCaseInsensitive()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByCaseInsensitiveName(true));
$expected = ['foo'];
if ('\\' === \DIRECTORY_SEPARATOR) {
$expected[] = 'foo bar';
$expected[] = 'foo/bar.tmp';
} else {
$expected[] = 'foo/bar.tmp';
$expected[] = 'foo bar';
}
$expected = array_merge($expected, [
'qux',
'qux/baz_1_2.py',
'qux/baz_100_1.py',
'qux_0_1.php',
'qux_2_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_1000_1.php',
'qux_1002_0.php',
'test.php',
'test.py',
'toto',
'zebulon.php',
'Zephire.php',
]);
$this->assertOrderedIterator($this->toAbsolute($expected), $finder->in(self::$tmpDir)->getIterator());
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sortByCaseInsensitiveName(false));
$this->assertOrderedIterator($this->toAbsolute([
'foo',
'foo bar',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'zebulon.php',
'Zephire.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSort()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->sort(static fn (\SplFileInfo $a, \SplFileInfo $b) => strcmp($a->getRealPath(), $b->getRealPath())));
$this->assertOrderedIterator($this->toAbsolute([
'Zephire.php',
'foo',
'foo bar',
'foo/bar.tmp',
'qux',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'test.py',
'toto',
'zebulon.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testSortAcrossDirectories()
{
$finder = $this->buildFinder()
->in([
self::$tmpDir,
self::$tmpDir.'/qux',
self::$tmpDir.'/foo',
])
->depth(0)
->files()
->filter(static fn (\SplFileInfo $file): bool => '' !== $file->getExtension())
->sort(static fn (\SplFileInfo $a, \SplFileInfo $b): int => strcmp($a->getExtension(), $b->getExtension()) ?: strcmp($a->getFilename(), $b->getFilename()))
;
$this->assertOrderedIterator($this->toAbsolute([
'Zephire.php',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'test.php',
'zebulon.php',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'test.py',
'foo/bar.tmp',
]), $finder->getIterator());
}
public function testFilter()
{
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->filter(static fn (\SplFileInfo $f) => str_contains($f, 'test')));
$this->assertIterator($this->toAbsolute(['test.php', 'test.py']), $finder->in(self::$tmpDir)->getIterator());
}
public function testFilterPrune()
{
$this->setupVfsProvider([
'x' => [
'a.php' => '',
'b.php' => '',
'd' => [
'u.php' => '',
],
'x' => [
'd' => [
'u2.php' => '',
],
],
],
'y' => [
'c.php' => '',
],
]);
$finder = $this->buildFinder();
$finder
->in($this->vfsScheme.'://x')
->filter(static fn (): bool => true, true) // does nothing
->filter(function (\SplFileInfo $file): bool {
$path = $this->stripSchemeFromVfsPath($file->getPathname());
$res = 'x/d' !== $path;
$this->vfsLog[] = [$path, 'exclude_filter', $res];
return $res;
}, true)
->filter(static fn (): bool => true, true); // does nothing
$this->assertSameVfsIterator([
'x/a.php',
'x/b.php',
'x/x',
'x/x/d',
'x/x/d/u2.php',
], $finder->getIterator());
// "x/d" directory must be pruned early
// "x/x/d" directory must not be pruned
$this->assertSame([
['x', 'is_dir', true],
['x', 'list_dir_open', ['a.php', 'b.php', 'd', 'x']],
['x/a.php', 'is_dir', false],
['x/a.php', 'exclude_filter', true],
['x/b.php', 'is_dir', false],
['x/b.php', 'exclude_filter', true],
['x/d', 'is_dir', true],
['x/d', 'exclude_filter', false],
['x/x', 'is_dir', true],
['x/x', 'exclude_filter', true], // from ExcludeDirectoryFilterIterator::accept() (prune directory filter)
['x/x', 'exclude_filter', true], // from CustomFilterIterator::accept() (regular filter)
['x/x', 'list_dir_open', ['d']],
['x/x/d', 'is_dir', true],
['x/x/d', 'exclude_filter', true],
['x/x/d', 'list_dir_open', ['u2.php']],
['x/x/d/u2.php', 'is_dir', false],
['x/x/d/u2.php', 'exclude_filter', true],
], $this->vfsLog);
}
public function testFollowLinks()
{
if ('\\' == \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('symlinks are not supported on Windows');
}
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->followLinks());
$this->assertIterator($this->toAbsolute([
'foo',
'foo/bar.tmp',
'test.php',
'test.py',
'toto',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]), $finder->in(self::$tmpDir)->getIterator());
}
public function testUseUnixPaths()
{
$fixturesDirectory = __DIR__.\DIRECTORY_SEPARATOR.'Fixtures';
// Fix __DIR__ on Windows giving us backslashes.
$fixturesDirectory = str_replace('\\', '/', $fixturesDirectory);
$finder = $this->buildFinder();
$this->assertSame($finder, $finder->useUnixPaths());
foreach ($finder->in($fixturesDirectory) as $file) {
$this->assertStringNotContainsString('\\', $file->getPathname(), 'Paths should be in UNIX style.');
}
}
public function testIn()
{
$finder = $this->buildFinder();
$iterator = $finder->files()->name('*.php')->depth('< 1')->in([self::$tmpDir, __DIR__])->getIterator();
$expected = [
self::$tmpDir.\DIRECTORY_SEPARATOR.'Zephire.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'test.php',
__DIR__.\DIRECTORY_SEPARATOR.'GitignoreTest.php',
__DIR__.\DIRECTORY_SEPARATOR.'FinderOpenBasedirTest.php',
__DIR__.\DIRECTORY_SEPARATOR.'FinderTest.php',
__DIR__.\DIRECTORY_SEPARATOR.'GlobTest.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_0_1.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_1000_1.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_1002_0.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_10_2.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_12_0.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_2_0.php',
self::$tmpDir.\DIRECTORY_SEPARATOR.'zebulon.php',
];
$this->assertIterator($expected, $iterator);
}
public function testInWithNonExistentDirectory()
{
$this->expectException(DirectoryNotFoundException::class);
$finder = new Finder();
$finder->in('foobar');
}
public function testInWithNonExistentDirectoryLegacyException()
{
$this->expectException(\InvalidArgumentException::class);
$finder = new Finder();
$finder->in('foobar');
}
public function testInWithGlob()
{
$finder = $this->buildFinder();
$finder->in([__DIR__.'/Fixtures/*/B/C/', __DIR__.'/Fixtures/*/*/B/C/'])->getIterator();
$this->assertIterator($this->toAbsoluteFixtures(['A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy']), $finder);
}
public function testInWithNonDirectoryGlob()
{
$this->expectException(\InvalidArgumentException::class);
$finder = new Finder();
$finder->in(__DIR__.'/Fixtures/A/a*');
}
public function testInWithGlobBrace()
{
if (!\defined('GLOB_BRACE')) {
$this->markTestSkipped('Glob brace is not supported on this system.');
}
$finder = $this->buildFinder();
$finder->in([__DIR__.'/Fixtures/{A,copy/A}/B/C'])->getIterator();
$this->assertIterator($this->toAbsoluteFixtures(['A/B/C/abc.dat', 'copy/A/B/C/abc.dat.copy']), $finder);
}
public function testGetIteratorWithoutIn()
{
$this->expectException(\LogicException::class);
$finder = Finder::create();
$finder->getIterator();
}
public function testGetIterator()
{
$finder = $this->buildFinder();
$dirs = [];
foreach ($finder->directories()->in(self::$tmpDir) as $dir) {
$dirs[] = (string) $dir;
}
$expected = $this->toAbsolute(['foo', 'qux', 'toto']);
sort($dirs);
sort($expected);
$this->assertEquals($expected, $dirs, 'implements the \IteratorAggregate interface');
$finder = $this->buildFinder();
$this->assertEquals(3, iterator_count($finder->directories()->in(self::$tmpDir)), 'implements the \IteratorAggregate interface');
$finder = $this->buildFinder();
$a = iterator_to_array($finder->directories()->in(self::$tmpDir));
$a = array_values(array_map('strval', $a));
sort($a);
$this->assertEquals($expected, $a, 'implements the \IteratorAggregate interface');
}
public function testRelativePath()
{
$finder = $this->buildFinder()->in(self::$tmpDir);
$paths = [];
foreach ($finder as $file) {
$paths[] = $file->getRelativePath();
}
$ref = ['', '', '', '', '', '', '', '', '', '', '', '', '', 'foo', 'qux', 'qux', ''];
sort($ref);
sort($paths);
$this->assertEquals($ref, $paths);
}
public function testRelativePathname()
{
$finder = $this->buildFinder()->in(self::$tmpDir)->sortByName();
$paths = [];
foreach ($finder as $file) {
$paths[] = $file->getRelativePathname();
}
$ref = [
'Zephire.php',
'test.php',
'toto',
'test.py',
'foo',
'foo'.\DIRECTORY_SEPARATOR.'bar.tmp',
'foo bar',
'qux',
'qux'.\DIRECTORY_SEPARATOR.'baz_100_1.py',
'qux'.\DIRECTORY_SEPARATOR.'baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php',
];
sort($paths);
sort($ref);
$this->assertEquals($ref, $paths);
}
public function testGetFilenameWithoutExtension()
{
$finder = $this->buildFinder()->in(self::$tmpDir)->sortByName();
$fileNames = [];
foreach ($finder as $file) {
$fileNames[] = $file->getFilenameWithoutExtension();
}
$ref = [
'Zephire',
'test',
'toto',
'test',
'foo',
'bar',
'foo bar',
'qux',
'baz_100_1',
'baz_1_2',
'qux_0_1',
'qux_1000_1',
'qux_1002_0',
'qux_10_2',
'qux_12_0',
'qux_2_0',
'zebulon',
];
sort($fileNames);
sort($ref);
$this->assertEquals($ref, $fileNames);
}
public function testAppendWithAFinder()
{
$finder = $this->buildFinder();
$finder->files()->in(self::$tmpDir.\DIRECTORY_SEPARATOR.'foo');
$finder1 = $this->buildFinder();
$finder1->directories()->in(self::$tmpDir);
$finder = $finder->append($finder1);
$this->assertIterator($this->toAbsolute(['foo', 'foo/bar.tmp', 'qux', 'toto']), $finder->getIterator());
}
public function testAppendWithAnArray()
{
$finder = $this->buildFinder();
$finder->files()->in(self::$tmpDir.\DIRECTORY_SEPARATOR.'foo');
$finder->append($this->toAbsolute(['foo', 'toto']));
$this->assertIterator($this->toAbsolute(['foo', 'foo/bar.tmp', 'toto']), $finder->getIterator());
}
public function testAppendStandardizesItemsToBeSymfonySplFileInfo()
{
$finder1 = $this->buildFinder();
$finder1->files()->in(self::$tmpDir.\DIRECTORY_SEPARATOR.'foo');
$finder2 = $this->buildFinder();
$finder2->directories()->in(self::$tmpDir);
$finder1->append($finder2);
$finder1->append($this->toAbsolute(['foo']));
$finder1->append(array_map(static fn ($item) => new \SplFileInfo($item), $this->toAbsolute(['toto'])));
foreach ($finder1 as $item) {
$this->assertInstanceOf(SplFileInfo::class, $item);
}
}
public function testRelativePathWithoutAppend()
{
$this->setupVfsProvider([
'a' => [
'a1' => '',
'a2' => '',
'b' => [
'b1' => '',
'b2' => '',
'c' => [
'c1' => '',
'c2' => '',
],
],
],
]);
$dir = $this->vfsScheme.'://';
$finder = Finder::create()->sortByName()->in($dir.'a/b');
$this->assertSame(
[
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b1', 'relativePathname' => 'b1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b2', 'relativePathname' => 'b2'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c', 'relativePathname' => 'c'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c1', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c2', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c2'],
],
self::formatForAssert($finder),
);
}
public function testRelativePathWithAppendedFinderForParentDirectory()
{
$this->setupVfsProvider([
'a' => [
'a1' => '',
'a2' => '',
'b' => [
'b1' => '',
'b2' => '',
'c' => [
'c1' => '',
'c2' => '',
],
],
],
]);
$dir = $this->vfsScheme.'://';
$finder = Finder::create()->sortByName(true)->in($dir.'a/b');
$finder->append(Finder::create()->in($dir.'a'));
$expected = [
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'a1', 'relativePathname' => 'a1'],
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'a2', 'relativePathname' => 'a2'],
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'b', 'relativePathname' => 'b'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b1', 'relativePathname' => 'b1'],
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'b1', 'relativePathname' => 'b'.\DIRECTORY_SEPARATOR.'b1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b2', 'relativePathname' => 'b2'],
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'b2', 'relativePathname' => 'b'.\DIRECTORY_SEPARATOR.'b2'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c', 'relativePathname' => 'c'],
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'c', 'relativePathname' => 'b'.\DIRECTORY_SEPARATOR.'c'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c1', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c1'],
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c1', 'relativePathname' => 'b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c2', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c2'],
['key' => $dir.'a'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c2', 'relativePathname' => 'b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c2'],
];
if ('\\' === \DIRECTORY_SEPARATOR) {
usort($expected, static fn ($a, $b) => $a['key'] <=> $b['key']);
}
$this->assertSame($expected, self::formatForAssert($finder));
}
public function testRelativePathWithAppendedFinderForChildDirectory()
{
$this->setupVfsProvider([
'a' => [
'a1' => '',
'a2' => '',
'b' => [
'b1' => '',
'b2' => '',
'c' => [
'c1' => '',
'c2' => '',
],
],
],
]);
$dir = $this->vfsScheme.'://';
$finder = Finder::create()->sortByName(true)->in($dir.'a/b');
$finder->append(Finder::create()->in($dir.'a/b/c'));
$expected = [
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b1', 'relativePathname' => 'b1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b2', 'relativePathname' => 'b2'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c', 'relativePathname' => 'c'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c1', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c1'],
['key' => $dir.'a/b/c'.\DIRECTORY_SEPARATOR.'c1', 'relativePathname' => 'c1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c2', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c2'],
['key' => $dir.'a/b/c'.\DIRECTORY_SEPARATOR.'c2', 'relativePathname' => 'c2'],
];
if ('\\' === \DIRECTORY_SEPARATOR) {
usort($expected, static fn ($a, $b) => $a['key'] <=> $b['key']);
}
$this->assertSame($expected, self::formatForAssert($finder));
}
public function testRelativePathWithAppendedPaths()
{
$this->setupVfsProvider([
'a' => [
'a1' => '',
'a2' => '',
'b' => [
'b1' => '',
'b2' => '',
'c' => [
'c1' => '',
'c2' => '',
],
],
],
]);
$dir = $this->vfsScheme.'://';
$finder = Finder::create()->sortByName(true)->in($dir.'a/b');
$finder->append([$dir.'a/a1', $dir.'a/b/c/c1']);
$expected = [
['key' => $dir.'a/a1', 'relativePathname' => $dir.'a/a1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b1', 'relativePathname' => 'b1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'b2', 'relativePathname' => 'b2'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c', 'relativePathname' => 'c'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c1', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c1'],
['key' => $dir.'a/b/c/c1', 'relativePathname' => $dir.'a/b/c/c1'],
['key' => $dir.'a/b'.\DIRECTORY_SEPARATOR.'c'.\DIRECTORY_SEPARATOR.'c2', 'relativePathname' => 'c'.\DIRECTORY_SEPARATOR.'c2'],
];
if ('\\' === \DIRECTORY_SEPARATOR) {
usort($expected, static fn ($a, $b) => $a['key'] <=> $b['key']);
}
$this->assertSame($expected, self::formatForAssert($finder));
}
public function testRelativePathWithAppendedOnEmptyFinder()
{
$this->setupVfsProvider([
'a' => [
'a1' => '',
'a2' => '',
'b' => [
'b1' => '',
'b2' => '',
'c' => [
'c1' => '',
'c2' => '',
],
],
],
]);
$dir = $this->vfsScheme.'://';
$finder = Finder::create()->sortByName();
$finder->append([$dir.'a/a1']);
$this->assertSame(
[
['key' => $dir.'a/a1', 'relativePathname' => $dir.'a/a1'],
],
self::formatForAssert($finder),
);
}
public function testAppendReturnsAFinder()
{
$this->assertInstanceOf(Finder::class, Finder::create()->append([]));
}
public function testAppendEmptyIterableAllowsIteration()
{
$finder = Finder::create()->files()->name('*.php')->append([]);
$this->assertSame([], iterator_to_array($finder->getIterator()));
}
public function testAppendDoesNotRequireIn()
{
$finder = $this->buildFinder();
$finder->in(self::$tmpDir.\DIRECTORY_SEPARATOR.'foo');
$finder1 = Finder::create()->append($finder);
$this->assertIterator(iterator_to_array($finder->getIterator()), $finder1->getIterator());
}
public function testMultipleAppendCallsWithSorting()
{
$finder = $this->buildFinder()
->sortByName()
->append([self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_1000_1.php'])
->append([self::$tmpDir.\DIRECTORY_SEPARATOR.'qux_1002_0.php'])
;
$this->assertOrderedIterator($this->toAbsolute(['qux_1000_1.php', 'qux_1002_0.php']), $finder->getIterator());
}
public function testCountDirectories()
{
$directory = Finder::create()->directories()->in(self::$tmpDir);
$i = 0;
foreach ($directory as $dir) {
++$i;
}
$this->assertCount($i, $directory);
}
public function testCountFiles()
{
$files = Finder::create()->files()->in(__DIR__.\DIRECTORY_SEPARATOR.'Fixtures');
$i = 0;
foreach ($files as $file) {
++$i;
}
$this->assertCount($i, $files);
}
public function testCountWithoutIn()
{
$this->expectException(\LogicException::class);
$finder = Finder::create()->files();
\count($finder);
}
public function testHasResults()
{
$finder = $this->buildFinder();
$finder->in(__DIR__);
$this->assertTrue($finder->hasResults());
}
public function testNoResults()
{
$finder = $this->buildFinder();
$finder->in(__DIR__)->name('DoesNotExist');
$this->assertFalse($finder->hasResults());
}
#[DataProvider('getContainsTestData')]
public function testContains($matchPatterns, $noMatchPatterns, $expected)
{
$finder = $this->buildFinder();
$finder->in(__DIR__.\DIRECTORY_SEPARATOR.'Fixtures')
->name('*.txt')->sortByName()
->contains($matchPatterns)
->notContains($noMatchPatterns);
$this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
}
public function testContainsOnDirectory()
{
$finder = $this->buildFinder();
$finder->in(__DIR__)
->directories()
->name('Fixtures')
->contains('abc');
$this->assertIterator([], $finder);
}
public function testNotContainsOnDirectory()
{
$finder = $this->buildFinder();
$finder->in(__DIR__)
->directories()
->name('Fixtures')
->notContains('abc');
$this->assertIterator([], $finder);
}
/**
* Searching in multiple locations involves AppendIterator which does an unnecessary rewind which leaves FilterIterator
* with inner FilesystemIterator in an invalid state.
*
* @see https://bugs.php.net/68557
*/
public function testMultipleLocations()
{
$locations = [
self::$tmpDir.'/',
self::$tmpDir.'/toto/',
];
// it is expected that there are test.py test.php in the tmpDir
$finder = new Finder();
$finder->in($locations)
// the default flag IGNORE_DOT_FILES fixes the problem indirectly
// so we set it to false for better isolation
->ignoreDotFiles(false)
->depth('< 1')->name('test.php');
$this->assertCount(1, $finder);
}
/**
* Searching in multiple locations with sub directories involves
* AppendIterator which does an unnecessary rewind which leaves
* FilterIterator with inner FilesystemIterator in an invalid state.
*
* @see https://bugs.php.net/68557
*/
public function testMultipleLocationsWithSubDirectories()
{
$locations = [
__DIR__.'/Fixtures/one',
self::$tmpDir.\DIRECTORY_SEPARATOR.'toto',
];
$finder = $this->buildFinder();
$finder->in($locations)->depth('< 10')->name('*.neon');
$expected = [
__DIR__.'/Fixtures/one'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'c.neon',
__DIR__.'/Fixtures/one'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'d.neon',
];
$this->assertIterator($expected, $finder);
$this->assertIteratorInForeach($expected, $finder);
}
/**
* Iterator keys must be the file pathname.
*/
public function testIteratorKeys()
{
$finder = $this->buildFinder()->in(self::$tmpDir);
foreach ($finder as $key => $file) {
$this->assertEquals($file->getPathname(), $key);
}
}
public function testRegexSpecialCharsLocationWithPathRestrictionContainingStartFlag()
{
$finder = $this->buildFinder();
$finder->in(__DIR__.\DIRECTORY_SEPARATOR.'Fixtures'.\DIRECTORY_SEPARATOR.'r+e.gex[c]a(r)s')
->path('/^dir/');
$expected = ['r+e.gex[c]a(r)s'.\DIRECTORY_SEPARATOR.'dir', 'r+e.gex[c]a(r)s'.\DIRECTORY_SEPARATOR.'dir'.\DIRECTORY_SEPARATOR.'bar.dat'];
$this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
}
public static function getContainsTestData()
{
return [
['', '', []],
['foo', 'bar', []],
['', 'foobar', ['dolor.txt', 'ipsum.txt', 'lorem.txt']],
['lorem ipsum dolor sit amet', 'foobar', ['lorem.txt']],
['sit', 'bar', ['dolor.txt', 'ipsum.txt', 'lorem.txt']],
['dolor sit amet', '@^L@m', ['dolor.txt', 'ipsum.txt']],
['/^lorem ipsum dolor sit amet$/m', 'foobar', ['lorem.txt']],
['lorem', 'foobar', ['lorem.txt']],
['', 'lorem', ['dolor.txt', 'ipsum.txt']],
['ipsum dolor sit amet', '/^IPSUM/m', ['lorem.txt']],
[['lorem', 'dolor'], [], ['lorem.txt', 'ipsum.txt', 'dolor.txt']],
['', ['lorem', 'ipsum'], ['dolor.txt']],
];
}
public static function getRegexNameTestData()
{
return [
['~.*t\\.p.+~i'],
['~t.*s~i'],
];
}
#[DataProvider('getTestPathData')]
public function testPath($matchPatterns, $noMatchPatterns, array $expected)
{
$finder = $this->buildFinder();
$finder->in(__DIR__.\DIRECTORY_SEPARATOR.'Fixtures')
->path($matchPatterns)
->notPath($noMatchPatterns);
$this->assertIterator($this->toAbsoluteFixtures($expected), $finder);
}
public static function getTestPathData()
{
return [
['', '', []],
['/^A\/B\/C/', '/C$/',
['A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C'.\DIRECTORY_SEPARATOR.'abc.dat'],
],
['/^A\/B/', 'foobar',
[
'A'.\DIRECTORY_SEPARATOR.'B',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'ab.dat',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C'.\DIRECTORY_SEPARATOR.'abc.dat',
],
],
['A/B/C', 'foobar',
[
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C'.\DIRECTORY_SEPARATOR.'abc.dat',
'copy'.\DIRECTORY_SEPARATOR.'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C',
'copy'.\DIRECTORY_SEPARATOR.'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C'.\DIRECTORY_SEPARATOR.'abc.dat.copy',
],
],
['A/B', 'foobar',
[
// dirs
'A'.\DIRECTORY_SEPARATOR.'B',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C',
'copy'.\DIRECTORY_SEPARATOR.'A'.\DIRECTORY_SEPARATOR.'B',
'copy'.\DIRECTORY_SEPARATOR.'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C',
// files
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'ab.dat',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C'.\DIRECTORY_SEPARATOR.'abc.dat',
'copy'.\DIRECTORY_SEPARATOR.'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'ab.dat.copy',
'copy'.\DIRECTORY_SEPARATOR.'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C'.\DIRECTORY_SEPARATOR.'abc.dat.copy',
],
],
['/^with space\//', 'foobar',
[
'with space'.\DIRECTORY_SEPARATOR.'foo.txt',
],
],
[
'/^A/',
['a.dat', 'abc.dat'],
[
'A',
'A'.\DIRECTORY_SEPARATOR.'B',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'ab.dat',
],
],
[
['/^A/', 'one'],
'foobar',
[
'A',
'A'.\DIRECTORY_SEPARATOR.'B',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C',
'A'.\DIRECTORY_SEPARATOR.'a.dat',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'ab.dat',
'A'.\DIRECTORY_SEPARATOR.'B'.\DIRECTORY_SEPARATOR.'C'.\DIRECTORY_SEPARATOR.'abc.dat',
'one',
'one'.\DIRECTORY_SEPARATOR.'a',
'one'.\DIRECTORY_SEPARATOR.'b',
'one'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'c.neon',
'one'.\DIRECTORY_SEPARATOR.'b'.\DIRECTORY_SEPARATOR.'d.neon',
],
],
];
}
public function testAccessDeniedException()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('chmod is not supported on Windows');
}
$finder = $this->buildFinder();
$finder->files()->in(self::$tmpDir);
// make 'foo' directory non-readable
$testDir = self::$tmpDir.\DIRECTORY_SEPARATOR.'foo';
chmod($testDir, 0o333);
if (false === $couldRead = is_readable($testDir)) {
try {
$this->assertIterator($this->toAbsolute(['foo bar', 'test.php', 'test.py']), $finder->getIterator());
$this->fail('Finder should throw an exception when opening a non-readable directory.');
} catch (\Exception $e) {
$expectedExceptionClass = 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException';
if ($e instanceof ExpectationFailedException) {
$this->fail(\sprintf("Expected exception:\n%s\nGot:\n%s\nWith comparison failure:\n%s", $expectedExceptionClass, 'PHPUnit\Framework\ExpectationFailedException', $e->getComparisonFailure()->getExpectedAsString()));
}
$this->assertInstanceOf($expectedExceptionClass, $e);
}
}
// restore original permissions
chmod($testDir, 0o777);
clearstatcache(true, $testDir);
if ($couldRead) {
$this->markTestSkipped('could read test files while test requires unreadable');
}
}
public function testIgnoredAccessDeniedException()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('chmod is not supported on Windows');
}
$finder = $this->buildFinder();
$finder->files()->ignoreUnreadableDirs()->in(self::$tmpDir);
// make 'foo' directory non-readable
$testDir = self::$tmpDir.\DIRECTORY_SEPARATOR.'foo';
chmod($testDir, 0o333);
if (false === ($couldRead = is_readable($testDir))) {
$this->assertIterator($this->toAbsolute([
'foo bar',
'test.php',
'test.py',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
]
), $finder->getIterator());
}
// restore original permissions
chmod($testDir, 0o777);
clearstatcache(true, $testDir);
if ($couldRead) {
$this->markTestSkipped('could read test files while test requires unreadable');
}
}
protected function buildFinder()
{
return Finder::create()->exclude('gitignore');
}
private static function formatForAssert(Finder $finder): array
{
$data = [];
foreach ($finder as $key => $value) {
$data[] = ['key' => $key, 'relativePathname' => $value->getRelativePathname()];
}
return $data;
}
}
================================================
FILE: Tests/Fixtures/.dot/a
================================================
================================================
FILE: Tests/Fixtures/.dot/b/c.neon
================================================
================================================
FILE: Tests/Fixtures/.dot/b/d.neon
================================================
================================================
FILE: Tests/Fixtures/copy/A/B/C/abc.dat.copy
================================================
================================================
FILE: Tests/Fixtures/copy/A/B/ab.dat.copy
================================================
================================================
FILE: Tests/Fixtures/copy/A/a.dat.copy
================================================
================================================
FILE: Tests/Fixtures/dolor.txt
================================================
dolor sit amet
DOLOR SIT AMET
================================================
FILE: Tests/Fixtures/gitignore/.gitignore
================================================
c.txt
================================================
FILE: Tests/Fixtures/gitignore/git_root/search_root/.gitignore
================================================
/a.txt
================================================
FILE: Tests/Fixtures/gitignore/git_root/search_root/b.txt
================================================
================================================
FILE: Tests/Fixtures/gitignore/git_root/search_root/dir/.gitignore
================================================
/b.txt
================================================
FILE: Tests/Fixtures/gitignore/git_root/search_root/dir/a.txt
================================================
================================================
FILE: Tests/Fixtures/gitignore/search_root/.gitignore
================================================
/a.txt
================================================
FILE: Tests/Fixtures/gitignore/search_root/a.txt
================================================
================================================
FILE: Tests/Fixtures/gitignore/search_root/b.txt
================================================
================================================
FILE: Tests/Fixtures/gitignore/search_root/dir/.gitignore
================================================
/b.txt
================================================
FILE: Tests/Fixtures/gitignore/search_root/dir/a.txt
================================================
================================================
FILE: Tests/Fixtures/gitignore/search_root/dir/b.txt
================================================
================================================
FILE: Tests/Fixtures/ipsum.txt
================================================
ipsum dolor sit amet
IPSUM DOLOR SIT AMET
================================================
FILE: Tests/Fixtures/lorem.txt
================================================
lorem ipsum dolor sit amet
LOREM IPSUM DOLOR SIT AMET
================================================
FILE: Tests/Fixtures/one/.dot
================================================
.dot
================================================
FILE: Tests/Fixtures/one/a
================================================
================================================
FILE: Tests/Fixtures/one/b/c.neon
================================================
================================================
FILE: Tests/Fixtures/one/b/d.neon
================================================
================================================
FILE: Tests/Fixtures/with space/foo.txt
================================================
================================================
FILE: Tests/GitignoreTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Finder\Gitignore;
/**
* @author Michael Voříšek <vorismi3@fel.cvut.cz>
*/
class GitignoreTest extends TestCase
{
#[DataProvider('provider')]
#[DataProvider('providerExtended')]
public function testToRegex(array $gitignoreLines, array $matchingCases, array $nonMatchingCases)
{
$patterns = implode("\n", $gitignoreLines);
$regex = Gitignore::toRegex($patterns);
$this->assertSame($regex, Gitignore::toRegex(implode("\r\n", $gitignoreLines)));
$this->assertSame($regex, Gitignore::toRegex(implode("\r", $gitignoreLines)));
foreach ($matchingCases as $matchingCase) {
$this->assertMatchesRegularExpression(
$regex,
$matchingCase,
\sprintf(
"Failed asserting path:\n%s\nmatches gitignore patterns:\n%s",
preg_replace('~^~m', ' ', $matchingCase),
preg_replace('~^~m', ' ', $patterns)
)
);
}
foreach ($nonMatchingCases as $nonMatchingCase) {
$this->assertDoesNotMatchRegularExpression(
$regex,
$nonMatchingCase,
\sprintf("Failed asserting path:\n%s\nNOT matching gitignore patterns:\n%s",
preg_replace('~^~m', ' ', $nonMatchingCase),
preg_replace('~^~m', ' ', $patterns)
)
);
}
}
public static function provider(): array
{
$cases = [
[
[''],
[],
['a', 'a/b', 'a/b/c', 'aa', 'm.txt', '.txt'],
],
[
['a', 'X'],
['a', 'a/b', 'a/b/c', 'X', 'b/a', 'b/c/a', 'a/X', 'a/X/y', 'b/a/X/y'],
['A', 'x', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa'],
],
[
['/a', 'x', 'd/'],
['a', 'a/b', 'a/b/c', 'x', 'a/x', 'a/x/y', 'b/a/x/y', 'd/', 'd/u', 'e/d/', 'e/d/u'],
['b/a', 'b/c/a', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa', 'e/d'],
],
[
['a/', 'x'],
['a/b', 'a/b/c', 'x', 'a/x', 'a/x/y', 'b/a/x/y'],
['a', 'b/a', 'b/c/a', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa'],
],
[
['*'],
['a', 'a/b', 'a/b/c', 'aa', 'm.txt', '.txt'],
[],
],
[
['/*'],
['a', 'a/b', 'a/b/c', 'aa', 'm.txt', '.txt'],
[],
],
[
['/a', 'm/*', 'o/**', 'p/**/', 'x**y'],
['a', 'a/b', 'a/b/c', 'm/', 'o/', 'p/', 'xy', 'xuy', 'x/y', 'x/u/y', 'xu/y', 'x/uy', 'xu/uy'],
['aa', 'm', 'b/m', 'b/m/', 'o', 'b/o', 'b/o/', 'p', 'b/p', 'b/p/'],
],
[
['a', '!x'],
['a', 'a/b', 'a/b/c', 'b/a', 'b/c/a'],
['x', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa'],
],
[
['a', '!a/', 'b', '!b/b'],
['a', 'a/x', 'x/a', 'x/a/x', 'b', 'b'],
['a/', 'x/a/', 'bb', 'b/b', 'bb'],
],
[
['[a-c]', 'x[C-E][][o]', 'g-h'],
['a', 'b', 'c', 'xDo', 'g-h'],
['A', 'xdo', 'u', 'g', 'h'],
],
[
['a?', '*/??b?'],
['ax', 'x/xxbx'],
['a', 'axy', 'xxax', 'x/xxax', 'x/y/xxax'],
],
[
[' ', ' \ ', ' \ ', '/a ', '/b/c \ '],
[' ', ' ', 'x/ ', 'x/ ', 'a', 'a/x', 'b/c '],
[' ', ' ', 'x/ ', 'x/ ', 'a ', 'b/c '],
],
[
['#', ' #', '/ #', ' #', '/ #', ' \ #', ' \ #', 'a #', 'a #', 'a \ #', 'a \ #'],
[' ', ' ', 'a', 'a ', 'a '],
[' ', ' ', 'a ', 'a '],
],
[
["\t", "\t\\\t", " \t\\\t ", "\t#", "a\t#", "a\t\t#", "a \t#", "a\t\t\\\t#", "a \t\t\\\t\t#"],
["\t\t", " \t\t", 'a', "a\t\t\t", "a \t\t\t"],
["\t", "\t\t ", " \t\t ", "a\t", 'a ', "a \t", "a\t\t"],
],
[
[' a', 'b ', '\ ', 'c\ '],
[' a', 'b', ' ', 'c '],
['a', 'b ', 'c'],
],
[
['#a', '\#b', '\#/'],
['#b', '#/'],
['#a', 'a', 'b'],
],
[
['*', '!!', '!!*x', '\!!b'],
['a', '!!', '!!b'],
['!', '!x', '!xx'],
],
[
[
'*',
'!/bin',
'!/bin/bash',
],
['bin/cat', 'abc/bin/cat'],
['bin/bash'],
],
[
['fi#le.txt'],
[],
['#file.txt'],
],
[
[
'/bin/',
'/usr/local/',
'!/bin/bash',
'!/usr/local/bin/bash',
],
['bin/cat'],
['bin/bash'],
],
[
['*.py[co]'],
['file.pyc', 'file.pyc'],
['filexpyc', 'file.pycx', 'file.py'],
],
[
['dir1/**/dir2/'],
['dir1/dir2/', 'dir1/dirA/dir2/', 'dir1/dirA/dirB/dir2/'],
['dir1dir2/', 'dir1xdir2/', 'dir1/xdir2/', 'dir1x/dir2/'],
],
[
['dir1/*/dir2/'],
['dir1/dirA/dir2/'],
['dir1/dirA/dirB/dir2/'],
],
[
['/*.php'],
['file.php'],
['app/file.php'],
],
[
['\#file.txt'],
['#file.txt'],
[],
],
[
['*.php'],
['app/file.php', 'file.php'],
['file.phps', 'file.phps', 'filephps'],
],
[
['app/cache/'],
['app/cache/file.txt', 'app/cache/dir1/dir2/file.txt'],
['a/app/cache/file.txt'],
],
[
['#IamComment', '/app/cache/'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt'],
['a/app/cache/file.txt', '#IamComment', 'IamComment'],
],
[
['/app/cache/', '#LastLineIsComment'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt'],
['a/app/cache/file.txt', '#LastLineIsComment', 'LastLineIsComment'],
],
[
['/app/cache/', '\#file.txt', '#LastLineIsComment'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt', '#file.txt'],
['a/app/cache/file.txt', '#LastLineIsComment', 'LastLineIsComment'],
],
[
['/app/cache/', '\#file.txt', '#IamComment', 'another_file.txt'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt', '#file.txt', 'another_file.txt'],
['a/app/cache/file.txt', 'IamComment', '#IamComment'],
],
[
[
'/app/**',
'!/app/bin',
'!/app/bin/test',
],
['app/test/file', 'app/bin/file'],
['app/bin/test'],
],
[
[
'/app/*/img',
'!/app/*/img/src',
],
['app/a/img', 'app/a/img/x', 'app/a/img/src/x'],
['app/a/img/src', 'app/a/img/src/'],
],
[
[
'app/**/img',
'!/app/**/img/src',
],
['app/a/img', 'app/a/img/x', 'app/a/img/src/x', 'app/a/b/img', 'app/a/b/img/x', 'app/a/b/img/src/x', 'app/a/b/c/img'],
['app/a/img/src', 'app/a/b/img/src', 'app/a/c/b/img/src'],
],
[
[
'/*',
'!/foo',
'/foo/*',
'!/foo/bar',
],
['bar', 'foo/ba', 'foo/barx', 'x/foo/bar'],
['foo', 'foo/bar'],
],
[
[
'/example/**',
'!/example/example.txt',
'!/example/packages',
],
['example/test', 'example/example.txt2', 'example/packages/foo.yaml'],
['example/example.txt', 'example/packages', 'example/packages/'],
],
// based on https://www.atlassian.com/git/tutorials/saving-changes/gitignore
[
['**/logs'],
['logs/debug.log', 'logs/monday/foo.bar'],
[],
],
[
['**/logs/debug.log'],
['logs/debug.log', 'build/logs/debug.log'],
['logs/build/debug.log'],
],
[
['*.log'],
['debug.log', 'foo.log', '.log', 'logs/debug.log'],
[],
],
[
[
'*.log',
'!important.log',
],
['debug.log', 'trace.log'],
['important.log', 'logs/important.log'],
],
[
[
'*.log',
'!important/*.log',
'trace.*',
],
['debug.log', 'important/trace.log'],
['important/debug.log'],
],
[
['/debug.log'],
['debug.log'],
['logs/debug.log'],
],
[
['debug.log'],
['debug.log', 'logs/debug.log'],
[],
],
[
['debug?.log'],
['debug0.log', 'debugg.log'],
['debug10.log'],
],
[
['debug[0-9].log'],
['debug0.log', 'debug1.log'],
['debug10.log'],
],
[
['debug[01].log'],
['debug0.log', 'debug1.log'],
['debug2.log', 'debug01.log'],
],
[
['debug[!01].log'],
['debug2.log'],
['debug0.log', 'debug1.log', 'debug01.log'],
],
[
['debug[a-z].log'],
['debuga.log', 'debugb.log'],
['debug1.log'],
],
[
['logs'],
['logs', 'logs/debug.log', 'logs/latest/foo.bar', 'build/logs', 'build/logs/debug.log'],
[],
],
[
['logs/'],
['logs/debug.log', 'logs/latest/foo.bar', 'build/logs/foo.bar', 'build/logs/latest/debug.log'],
[],
],
[
[
'logs/',
'!logs/important.log',
],
['logs/debug.log'/* must be pruned on traversal 'logs/important.log' */],
[],
],
[
['logs/**/debug.log'],
['logs/debug.log', 'logs/monday/debug.log', 'logs/monday/pm/debug.log'],
[],
],
[
['logs/*day/debug.log'],
['logs/monday/debug.log', 'logs/tuesday/debug.log'],
['logs/latest/debug.log'],
],
[
['logs/debug.log'],
['logs/debug.log'],
['debug.log', 'build/logs/debug.log'],
],
[
['*/vendor/*'],
['a/vendor/', 'a/vendor/b', 'a/vendor/b/c'],
['a', 'vendor', 'vendor/', 'a/vendor', 'a/b/vendor', 'a/b/vendor/c'],
],
[
['**/vendor/**'],
['vendor/', 'vendor/a', 'vendor/a/b', 'a/b/vendor/c/d'],
['a', 'vendor', 'a/vendor', 'a/b/vendor'],
],
[
['***/***/vendor/*****/*****'],
['vendor/', 'vendor/a', 'vendor/a/b', 'a/b/vendor/c/d'],
['a', 'vendor', 'a/vendor', 'a/b/vendor'],
],
[
['**vendor**'],
['vendor', 'vendor/', 'vendor/a', 'vendor/a/b', 'a/vendor', 'a/b/vendor', 'a/b/vendor/c/d'],
['a'],
],
];
return $cases;
}
public static function providerExtended(): array
{
$basicCases = self::provider();
$cases = [];
foreach ($basicCases as $case) {
$cases[] = [
array_merge(['never'], $case[0], ['!never']),
$case[1],
$case[2],
];
$cases[] = [
array_merge(['!*'], $case[0]),
$case[1],
$case[2],
];
$cases[] = [
array_merge(['*', '!*'], $case[0]),
$case[1],
$case[2],
];
$cases[] = [
array_merge(['never', '**/never2', 'never3/**'], $case[0]),
$case[1],
$case[2],
];
$cases[] = [
array_merge(['!never', '!**/never2', '!never3/**'], $case[0]),
$case[1],
$case[2],
];
$lines = [];
for ($i = 0; $i < 30; ++$i) {
foreach ($case[0] as $line) {
$lines[] = $line;
}
}
$cases[] = [
array_merge(['!never', '!**/never2', '!never3/**'], $lines),
$case[1],
$case[2],
];
}
return $cases;
}
#[DataProvider('provideNegatedPatternsCases')]
public function testToRegexMatchingNegatedPatterns(array $gitignoreLines, array $matchingCases, array $nonMatchingCases)
{
$patterns = implode("\n", $gitignoreLines);
$regex = Gitignore::toRegexMatchingNegatedPatterns($patterns);
$this->assertSame($regex, Gitignore::toRegexMatchingNegatedPatterns(implode("\r\n", $gitignoreLines)));
$this->assertSame($regex, Gitignore::toRegexMatchingNegatedPatterns(implode("\r", $gitignoreLines)));
foreach ($matchingCases as $matchingCase) {
$this->assertMatchesRegularExpression(
$regex,
$matchingCase,
\sprintf(
"Failed asserting path:\n%s\nmatches gitignore negated patterns:\n%s",
preg_replace('~^~m', ' ', $matchingCase),
preg_replace('~^~m', ' ', $patterns)
)
);
}
foreach ($nonMatchingCases as $nonMatchingCase) {
$this->assertDoesNotMatchRegularExpression(
$regex,
$nonMatchingCase,
\sprintf("Failed asserting path:\n%s\nNOT matching gitignore negated patterns:\n%s",
preg_replace('~^~m', ' ', $nonMatchingCase),
preg_replace('~^~m', ' ', $patterns)
)
);
}
}
public static function provideNegatedPatternsCases(): iterable
{
yield [
[''],
[],
['a', 'a/b', 'a/b/c', 'aa', 'm.txt', '.txt'],
];
yield [
['!a', '!X'],
['a', 'a/b', 'a/b/c', 'X', 'b/a', 'b/c/a', 'a/X', 'a/X/y', 'b/a/X/y'],
['A', 'x', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa'],
];
yield [
['!/a', '!x', '!d/'],
['a', 'a/b', 'a/b/c', 'x', 'a/x', 'a/x/y', 'b/a/x/y', 'd/', 'd/u', 'e/d/', 'e/d/u'],
['b/a', 'b/c/a', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa', 'e/d'],
];
yield [
['!a/', '!x'],
['a/b', 'a/b/c', 'x', 'a/x', 'a/x/y', 'b/a/x/y'],
['a', 'b/a', 'b/c/a', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa'],
];
yield [
['!*'],
['a', 'a/b', 'a/b/c', 'aa', 'm.txt', '.txt'],
[],
];
yield [
['!/*'],
['a', 'a/b', 'a/b/c', 'aa', 'm.txt', '.txt'],
[],
];
yield [
['!/a', '!m/*', '!o/**', '!p/**/', '!x**y'],
['a', 'a/b', 'a/b/c', 'm/', 'o/', 'p/', 'xy', 'xuy', 'x/y', 'x/u/y', 'xu/y', 'x/uy', 'xu/uy'],
['aa', 'm', 'b/m', 'b/m/', 'o', 'b/o', 'b/o/', 'p', 'b/p', 'b/p/'],
];
yield [
['!a', 'x'],
['a', 'a/b', 'a/b/c', 'b/a', 'b/c/a'],
['x', 'aa', 'm.txt', '.txt', 'aa/b', 'b/aa'],
];
yield [
['!a', 'a/', '!b', 'b/b'],
['a', 'a/x', 'x/a', 'x/a/x', 'b', 'b'],
['a/', 'x/a/', 'bb', 'b/b', 'bb'],
];
yield [
['![a-c]', '!x[C-E][][o]', '!g-h'],
['a', 'b', 'c', 'xDo', 'g-h'],
['A', 'xdo', 'u', 'g', 'h'],
];
yield [
['!a?', '!*/??b?'],
['ax', 'x/xxbx'],
['a', 'axy', 'xxax', 'x/xxax', 'x/y/xxax'],
];
yield [
['! ', '! \ ', '! \ ', '!/a ', '!/b/c \ '],
[' ', ' ', 'x/ ', 'x/ ', 'a', 'a/x', 'b/c '],
[' ', ' ', 'x/ ', 'x/ ', 'a ', 'b/c '],
];
yield [
['!\#', '! #', '!/ #', '! #', '!/ #', '! \ #', '! \ #', '!a #', '!a #', '!a \ #', '!a \ #'],
[' ', ' ', 'a', 'a ', 'a '],
[' ', ' ', 'a ', 'a '],
];
yield [
["!\t", "!\t\\\t", "! \t\\\t ", "!\t#", "!a\t#", "!a\t\t#", "!a \t#", "!a\t\t\\\t#", "!a \t\t\\\t\t#"],
["\t\t", " \t\t", 'a', "a\t\t\t", "a \t\t\t"],
["\t", "\t\t ", " \t\t ", "a\t", 'a ', "a \t", "a\t\t"],
];
yield [
['! a', '!b ', '!\ ', '!c\ '],
[' a', 'b', ' ', 'c '],
['a', 'b ', 'c'],
];
yield [
['!\#a', '!\#b', '!\#/'],
['#a', '#b', '#/'],
['a', 'b'],
];
yield [
['*', '!!', '!!*x', '\!!b'],
['!', '!x', '!xx'],
['a', '!!', '!!b'],
];
yield [
[
'*',
'!/bin',
'!/bin/bash',
],
['bin/bash', 'bin/cat'],
['abc/bin/cat'],
];
yield [
['!fi#le.txt'],
[],
['#file.txt'],
];
yield [
[
'/bin/',
'/usr/local/',
'!/bin/bash',
'!/usr/local/bin/bash',
],
['bin/bash'],
['bin/cat'],
];
yield [
['!*.py[co]'],
['file.pyc', 'file.pyc'],
['filexpyc', 'file.pycx', 'file.py'],
];
yield [
['!dir1/**/dir2/'],
['dir1/dir2/', 'dir1/dirA/dir2/', 'dir1/dirA/dirB/dir2/'],
['dir1dir2/', 'dir1xdir2/', 'dir1/xdir2/', 'dir1x/dir2/'],
];
yield [
['!dir1/*/dir2/'],
['dir1/dirA/dir2/'],
['dir1/dirA/dirB/dir2/'],
];
yield [
['!/*.php'],
['file.php'],
['app/file.php'],
];
yield [
['!\#file.txt'],
['#file.txt'],
[],
];
yield [
['!*.php'],
['app/file.php', 'file.php'],
['file.phps', 'file.phps', 'filephps'],
];
yield [
['!app/cache/'],
['app/cache/file.txt', 'app/cache/dir1/dir2/file.txt'],
['a/app/cache/file.txt'],
];
yield [
['#IamComment', '!/app/cache/'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt'],
['a/app/cache/file.txt', '#IamComment', 'IamComment'],
];
yield [
['!/app/cache/', '#LastLineIsComment'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt'],
['a/app/cache/file.txt', '#LastLineIsComment', 'LastLineIsComment'],
];
yield [
['!/app/cache/', '!\#file.txt', '#LastLineIsComment'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt', '#file.txt'],
['a/app/cache/file.txt', '#LastLineIsComment', 'LastLineIsComment'],
];
yield [
['!/app/cache/', '!\#file.txt', '#IamComment', '!another_file.txt'],
['app/cache/file.txt', 'app/cache/subdir/ile.txt', '#file.txt', 'another_file.txt'],
['a/app/cache/file.txt', 'IamComment', '#IamComment'],
];
yield [
[
'/app/**',
'!/app/bin',
'!/app/bin/test',
],
['app/bin/file', 'app/bin/test'],
['app/test/file'],
];
yield [
[
'/app/*/img',
'!/app/*/img/src',
],
['app/a/img/src', 'app/a/img/src/', 'app/a/img/src/x'],
['app/a/img', 'app/a/img/x'],
];
yield [
[
'app/**/img',
'!/app/**/img/src',
],
['app/a/img/src', 'app/a/b/img/src', 'app/a/c/b/img/src', 'app/a/img/src/x', 'app/a/b/img/src/x'],
['app/a/img', 'app/a/img/x', 'app/a/b/img', 'app/a/b/img/x', 'app/a/b/c/img'],
];
yield [
[
'/*',
'!/foo',
'/foo/*',
'!/foo/bar',
],
['foo', 'foo/bar'],
['bar', 'foo/ba', 'foo/barx', 'x/foo/bar'],
];
yield [
[
'/example/**',
'!/example/example.txt',
'!/example/packages',
],
['example/example.txt', 'example/packages', 'example/packages/', 'example/packages/foo.yaml'],
['example/test', 'example/example.txt2'],
];
// based on https://www.atlassian.com/git/tutorials/saving-changes/gitignore
yield [
['!**/logs'],
['logs/debug.log', 'logs/monday/foo.bar'],
[],
];
yield [
['!**/logs/debug.log'],
['logs/debug.log', 'build/logs/debug.log'],
['logs/build/debug.log'],
];
yield [
['!*.log'],
['debug.log', 'foo.log', '.log', 'logs/debug.log'],
[],
];
yield [
[
'*.log',
'!important.log',
],
['important.log', 'logs/important.log'],
['debug.log', 'trace.log'],
];
yield [
[
'*.log',
'!important/*.log',
'trace.*',
],
['important/debug.log'],
['debug.log', 'important/trace.log'],
];
yield [
['!/debug.log'],
['debug.log'],
['logs/debug.log'],
];
yield [
['!debug.log'],
['debug.log', 'logs/debug.log'],
[],
];
yield [
['!debug?.log'],
['debug0.log', 'debugg.log'],
['debug10.log'],
];
yield [
['!debug[0-9].log'],
['debug0.log', 'debug1.log'],
['debug10.log'],
];
yield [
['!debug[01].log'],
['debug0.log', 'debug1.log'],
['debug2.log', 'debug01.log'],
];
yield [
['!debug[!01].log'],
['debug2.log'],
['debug0.log', 'debug1.log', 'debug01.log'],
];
yield [
['!debug[a-z].log'],
['debuga.log', 'debugb.log'],
['debug1.log'],
];
yield [
['!logs'],
['logs', 'logs/debug.log', 'logs/latest/foo.bar', 'build/logs', 'build/logs/debug.log'],
[],
];
yield [
['!logs/'],
['logs/debug.log', 'logs/latest/foo.bar', 'build/logs/foo.bar', 'build/logs/latest/debug.log'],
[],
];
yield [
[
'logs/',
'!logs/important.log',
],
[],
['logs/debug.log'/* must be pruned on traversal 'logs/important.log' */],
];
yield [
['!logs/**/debug.log'],
['logs/debug.log', 'logs/monday/debug.log', 'logs/monday/pm/debug.log'],
[],
];
yield [
['!logs/*day/debug.log'],
['logs/monday/debug.log', 'logs/tuesday/debug.log'],
['logs/latest/debug.log'],
];
yield [
['!logs/debug.log'],
['logs/debug.log'],
['debug.log', 'build/logs/debug.log'],
];
yield [
['!*/vendor/*'],
['a/vendor/', 'a/vendor/b', 'a/vendor/b/c'],
['a', 'vendor', 'vendor/', 'a/vendor', 'a/b/vendor', 'a/b/vendor/c'],
];
yield [
['!**/vendor/**'],
['vendor/', 'vendor/a', 'vendor/a/b', 'a/b/vendor/c/d'],
['a', 'vendor', 'a/vendor', 'a/b/vendor'],
];
yield [
['!***/***/vendor/*****/*****'],
['vendor/', 'vendor/a', 'vendor/a/b', 'a/b/vendor/c/d'],
['a', 'vendor', 'a/vendor', 'a/b/vendor'],
];
yield [
['!**vendor**'],
['vendor', 'vendor/', 'vendor/a', 'vendor/a/b', 'a/vendor', 'a/b/vendor', 'a/b/vendor/c/d'],
['a'],
];
}
}
================================================
FILE: Tests/GlobTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\Glob;
class GlobTest extends TestCase
{
public function testGlobToRegexDelimiters()
{
$this->assertEquals('#^(?=[^\.])\#$#', Glob::toRegex('#'));
$this->assertEquals('#^\.[^/]*$#', Glob::toRegex('.*'));
$this->assertEquals('^\.[^/]*$', Glob::toRegex('.*', true, true, ''));
$this->assertEquals('/^\.[^/]*$/', Glob::toRegex('.*', true, true, '/'));
}
public function testGlobToRegexDoubleStarStrictDots()
{
$finder = new Finder();
$finder->ignoreDotFiles(false);
$regex = Glob::toRegex('/**/*.neon');
foreach ($finder->in(__DIR__) as $k => $v) {
$k = str_replace(\DIRECTORY_SEPARATOR, '/', $k);
if (preg_match($regex, substr($k, \strlen(__DIR__)))) {
$match[] = substr($k, 10 + \strlen(__DIR__));
}
}
sort($match);
$this->assertSame(['one/b/c.neon', 'one/b/d.neon'], $match);
}
public function testGlobToRegexDoubleStarNonStrictDots()
{
$finder = new Finder();
$finder->ignoreDotFiles(false);
$regex = Glob::toRegex('/**/*.neon', false);
foreach ($finder->in(__DIR__) as $k => $v) {
$k = str_replace(\DIRECTORY_SEPARATOR, '/', $k);
if (preg_match($regex, substr($k, \strlen(__DIR__)))) {
$match[] = substr($k, 10 + \strlen(__DIR__));
}
}
sort($match);
$this->assertSame(['.dot/b/c.neon', '.dot/b/d.neon', 'one/b/c.neon', 'one/b/d.neon'], $match);
}
public function testGlobToRegexDoubleStarWithoutLeadingSlash()
{
$finder = new Finder();
$finder->ignoreDotFiles(false);
$regex = Glob::toRegex('/Fixtures/one/**');
foreach ($finder->in(__DIR__) as $k => $v) {
$k = str_replace(\DIRECTORY_SEPARATOR, '/', $k);
if (preg_match($regex, substr($k, \strlen(__DIR__)))) {
$match[] = substr($k, 10 + \strlen(__DIR__));
}
}
sort($match);
$this->assertSame(['one/a', 'one/b', 'one/b/c.neon', 'one/b/d.neon'], $match);
}
public function testGlobToRegexDoubleStarWithoutLeadingSlashNotStrictLeadingDot()
{
$finder = new Finder();
$finder->ignoreDotFiles(false);
$regex = Glob::toRegex('/Fixtures/one/**', false);
foreach ($finder->in(__DIR__) as $k => $v) {
$k = str_replace(\DIRECTORY_SEPARATOR, '/', $k);
if (preg_match($regex, substr($k, \strlen(__DIR__)))) {
$match[] = substr($k, 10 + \strlen(__DIR__));
}
}
sort($match);
$this->assertSame(['one/.dot', 'one/a', 'one/b', 'one/b/c.neon', 'one/b/d.neon'], $match);
}
public function testGlobToRegexDoubleStarMatchesRootFiles()
{
$regex = Glob::toRegex('**/*.txt');
$this->assertSame(1, preg_match($regex, 'file.txt'));
$this->assertSame(1, preg_match($regex, 'foo/file.txt'));
$this->assertSame(1, preg_match($regex, 'foo/bar/baz.txt'));
$this->assertSame(1, preg_match($regex, '/foo/bar.txt'));
$this->assertSame(0, preg_match($regex, './foo/bar.txt'));
$this->assertSame(0, preg_match($regex, 'foo/.bar/bar.txt'));
$this->assertSame(0, preg_match($regex, '.file.txt'));
$this->assertSame(0, preg_match($regex, 'foo/bar/baz.php'));
$regex = Glob::toRegex('**/*.txt', false);
$this->assertSame(1, preg_match($regex, './foo/bar.txt'));
$this->assertSame(1, preg_match($regex, 'foo/.bar/bar.txt'));
$this->assertSame(1, preg_match($regex, '.file.txt'));
}
}
================================================
FILE: Tests/Iterator/CustomFilterIteratorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
class CustomFilterIteratorTest extends IteratorTestCase
{
public function testWithInvalidFilter()
{
$this->expectException(\InvalidArgumentException::class);
new CustomFilterIterator(new Iterator(), ['foo']);
}
#[DataProvider('getAcceptData')]
public function testAccept($filters, $expected)
{
$inner = new Iterator(['test.php', 'test.py', 'foo.php']);
$iterator = new CustomFilterIterator($inner, $filters);
$this->assertIterator($expected, $iterator);
}
public static function getAcceptData()
{
return [
[[static fn (\SplFileInfo $fileinfo) => false], []],
[[static fn (\SplFileInfo $fileinfo) => str_starts_with($fileinfo, 'test')], ['test.php', 'test.py']],
[['is_dir'], []],
];
}
}
================================================
FILE: Tests/Iterator/DateRangeFilterIteratorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
class DateRangeFilterIteratorTest extends RealIteratorTestCase
{
#[DataProvider('getAcceptData')]
public function testAccept($size, $expected)
{
$files = self::$files;
$files[] = static::toAbsolute('doesnotexist');
$inner = new Iterator($files);
$iterator = new DateRangeFilterIterator($inner, $size);
$this->assertIterator($expected, $iterator);
}
public static function getAcceptData()
{
$since20YearsAgo = [
'.git',
'test.py',
'foo',
'foo/bar.tmp',
'test.php',
'toto',
'toto/.git',
'.bar',
'.foo',
'.foo/.bar',
'foo bar',
'.foo/bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
];
$since2MonthsAgo = [
'.git',
'test.py',
'foo',
'toto',
'toto/.git',
'.bar',
'.foo',
'.foo/.bar',
'foo bar',
'.foo/bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
];
$untilLastMonth = [
'foo/bar.tmp',
'test.php',
];
return [
[[new DateComparator('since 20 years ago')], static::toAbsolute($since20YearsAgo)],
[[new DateComparator('since 2 months ago')], static::toAbsolute($since2MonthsAgo)],
[[new DateComparator('until last month')], static::toAbsolute($untilLastMonth)],
];
}
}
================================================
FILE: Tests/Iterator/DepthRangeFilterIteratorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
class DepthRangeFilterIteratorTest extends RealIteratorTestCase
{
#[DataProvider('getAcceptData')]
public function testAccept($minDepth, $maxDepth, $expected)
{
$inner = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new DepthRangeFilterIterator($inner, $minDepth, $maxDepth);
$actual = array_keys(iterator_to_array($iterator));
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual);
}
public static function getAcceptData()
{
$lessThan1 = [
'.git',
'test.py',
'foo',
'test.php',
'toto',
'.foo',
'.bar',
'foo bar',
'qux',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php', 'Zephire.php',
];
$lessThanOrEqualTo1 = [
'.git',
'test.py',
'foo',
'foo/bar.tmp',
'test.php',
'toto',
'toto/.git',
'.foo',
'.foo/.bar',
'.bar',
'foo bar',
'.foo/bar',
'qux',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
'zebulon.php', 'Zephire.php',
];
$graterThanOrEqualTo1 = [
'toto/.git',
'foo/bar.tmp',
'.foo/.bar',
'.foo/bar',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
];
$equalTo1 = [
'toto/.git',
'foo/bar.tmp',
'.foo/.bar',
'.foo/bar',
'qux/baz_100_1.py',
'qux/baz_1_2.py',
];
return [
[0, 0, self::toAbsolute($lessThan1)],
[0, 1, self::toAbsolute($lessThanOrEqualTo1)],
[2, \PHP_INT_MAX, []],
[1, \PHP_INT_MAX, self::toAbsolute($graterThanOrEqualTo1)],
[1, 1, self::toAbsolute($equalTo1)],
];
}
}
================================================
FILE: Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator;
class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase
{
#[DataProvider('getAcceptData')]
public function testAccept($directories, $expected)
{
$inner = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->toAbsolute(), \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
$iterator = new ExcludeDirectoryFilterIterator($inner, $directories);
$this->assertIterator($expected, $iterator);
}
public static function getAcceptData()
{
$foo = [
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'.git',
'test.py',
'test.php',
'toto',
'toto/.git',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
];
$fo = [
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'.git',
'test.py',
'foo',
'foo/bar.tmp',
'test.php',
'toto',
'toto/.git',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
];
$toto = [
'.bar',
'.foo',
'.foo/.bar',
'.foo/bar',
'.git',
'test.py',
'foo',
'foo/bar.tmp',
'test.php',
'foo bar',
'qux',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
];
return [
[['foo'], self::toAbsolute($foo)],
[['fo'], self::toAbsolute($fo)],
[['toto/'], self::toAbsolute($toto)],
];
}
}
================================================
FILE: Tests/Iterator/FileTypeFilterIteratorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\Iterator;
use PHPUnit\Framework\Attributes\DataProvider;
use Symfony\Component\Finder\Iterator\FileTypeFilterIterator;
class FileTypeFilterIteratorTest extends RealIteratorTestCase
{
#[DataProvider('getAcceptData')]
public function testAccept($mode, $expected)
{
$inner = new InnerTypeIterator(self::$files);
$iterator = new FileTypeFilterIterator($inner, $mode);
$this->assertIterator($expected, $iterator);
}
public static function getAcceptData()
{
$onlyFiles = [
'test.py',
'foo/bar.tmp',
'test.php',
'.bar',
'.foo/.bar',
'.foo/bar',
'foo bar',
'qux/baz_100_1.py',
'zebulon.php',
'Zephire.php',
'qux/baz_1_2.py',
'qux_0_1.php',
'qux_1000_1.php',
'qux_1002_0.php',
'qux_10_2.php',
'qux_12_0.php',
'qux_2_0.php',
];
$onlyDirectories = [
'.git',
'foo',
'qux',
'toto',
'toto/.git',
'.foo',
];
return [
[FileTypeFilterIterator::ONLY_FILES, self::toAbsolute($onlyFiles)],
[FileTypeFilterIterator::ONLY_DIRECTORIES, self::toAbsolute($onlyDirectories)],
];
}
}
class InnerTypeIterator extends \ArrayIterator
{
public function current(): \SplFileInfo
{
return new \SplFileInfo(parent::current());
}
public function isFile(): bool
{
return $this->current()->isFile();
}
public function isDir(): bool
{
return $this->current()->isDir();
}
}
================================================
FILE: Tests/Iterator/FilecontentFilterIteratorTest.php
================================================
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Tests\It
gitextract_xqh0_mj_/ ├── .gitattributes ├── .github/ │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── close-pull-request.yml ├── .gitignore ├── CHANGELOG.md ├── Comparator/ │ ├── Comparator.php │ ├── DateComparator.php │ └── NumberComparator.php ├── Exception/ │ ├── AccessDeniedException.php │ └── DirectoryNotFoundException.php ├── Finder.php ├── Gitignore.php ├── Glob.php ├── Iterator/ │ ├── CustomFilterIterator.php │ ├── DateRangeFilterIterator.php │ ├── DepthRangeFilterIterator.php │ ├── ExcludeDirectoryFilterIterator.php │ ├── FileTypeFilterIterator.php │ ├── FilecontentFilterIterator.php │ ├── FilenameFilterIterator.php │ ├── LazyIterator.php │ ├── MultiplePcreFilterIterator.php │ ├── PathFilterIterator.php │ ├── RecursiveDirectoryIterator.php │ ├── SizeRangeFilterIterator.php │ ├── SortableIterator.php │ └── VcsIgnoredFilterIterator.php ├── LICENSE ├── README.md ├── SplFileInfo.php ├── Tests/ │ ├── Comparator/ │ │ ├── ComparatorTest.php │ │ ├── DateComparatorTest.php │ │ └── NumberComparatorTest.php │ ├── FinderOpenBasedirTest.php │ ├── FinderTest.php │ ├── Fixtures/ │ │ ├── .dot/ │ │ │ ├── a │ │ │ └── b/ │ │ │ ├── c.neon │ │ │ └── d.neon │ │ ├── copy/ │ │ │ └── A/ │ │ │ ├── B/ │ │ │ │ ├── C/ │ │ │ │ │ └── abc.dat.copy │ │ │ │ └── ab.dat.copy │ │ │ └── a.dat.copy │ │ ├── dolor.txt │ │ ├── gitignore/ │ │ │ ├── .gitignore │ │ │ ├── git_root/ │ │ │ │ └── search_root/ │ │ │ │ ├── .gitignore │ │ │ │ ├── b.txt │ │ │ │ └── dir/ │ │ │ │ ├── .gitignore │ │ │ │ └── a.txt │ │ │ └── search_root/ │ │ │ ├── .gitignore │ │ │ ├── a.txt │ │ │ ├── b.txt │ │ │ └── dir/ │ │ │ ├── .gitignore │ │ │ ├── a.txt │ │ │ └── b.txt │ │ ├── ipsum.txt │ │ ├── lorem.txt │ │ ├── one/ │ │ │ ├── .dot │ │ │ ├── a │ │ │ └── b/ │ │ │ ├── c.neon │ │ │ └── d.neon │ │ └── with space/ │ │ └── foo.txt │ ├── GitignoreTest.php │ ├── GlobTest.php │ └── Iterator/ │ ├── CustomFilterIteratorTest.php │ ├── DateRangeFilterIteratorTest.php │ ├── DepthRangeFilterIteratorTest.php │ ├── ExcludeDirectoryFilterIteratorTest.php │ ├── FileTypeFilterIteratorTest.php │ ├── FilecontentFilterIteratorTest.php │ ├── FilenameFilterIteratorTest.php │ ├── InnerNameIterator.php │ ├── Iterator.php │ ├── IteratorTestCase.php │ ├── LazyIteratorTest.php │ ├── MockFileListIterator.php │ ├── MockSplFileInfo.php │ ├── MultiplePcreFilterIteratorTest.php │ ├── PathFilterIteratorTest.php │ ├── RealIteratorTestCase.php │ ├── RecursiveDirectoryIteratorTest.php │ ├── SizeRangeFilterIteratorTest.php │ ├── SortableIteratorTest.php │ ├── VcsIgnoredFilterIteratorTest.php │ └── VfsIteratorTestTrait.php ├── composer.json └── phpunit.xml.dist
SYMBOL INDEX (345 symbols across 51 files)
FILE: Comparator/Comparator.php
class Comparator (line 17) | class Comparator
method __construct (line 21) | public function __construct(
method getTarget (line 35) | public function getTarget(): string
method getOperator (line 43) | public function getOperator(): string
method test (line 51) | public function test(mixed $test): bool
FILE: Comparator/DateComparator.php
class DateComparator (line 19) | class DateComparator extends Comparator
method __construct (line 26) | public function __construct(string $test)
FILE: Comparator/NumberComparator.php
class NumberComparator (line 35) | class NumberComparator extends Comparator
method __construct (line 42) | public function __construct(?string $test)
FILE: Exception/AccessDeniedException.php
class AccessDeniedException (line 17) | class AccessDeniedException extends \UnexpectedValueException
FILE: Exception/DirectoryNotFoundException.php
class DirectoryNotFoundException (line 17) | class DirectoryNotFoundException extends \InvalidArgumentException
FILE: Finder.php
class Finder (line 42) | class Finder implements \IteratorAggregate, \Countable
method __construct (line 74) | public function __construct()
method create (line 82) | public static function create(): static
method directories (line 92) | public function directories(): static
method files (line 104) | public function files(): static
method depth (line 127) | public function depth(string|int|array $levels): static
method date (line 155) | public function date(string|array $dates): static
method name (line 180) | public function name(string|array $patterns): static
method notName (line 196) | public function notName(string|array $patterns): static
method contains (line 218) | public function contains(string|array $patterns): static
method notContains (line 240) | public function notContains(string|array $patterns): static
method path (line 264) | public function path(string|array $patterns): static
method notPath (line 288) | public function notPath(string|array $patterns): static
method size (line 310) | public function size(string|int|array $sizes): static
method exclude (line 332) | public function exclude(string|array $dirs): static
method ignoreDotFiles (line 348) | public function ignoreDotFiles(bool $ignoreDotFiles): static
method ignoreVCS (line 368) | public function ignoreVCS(bool $ignoreVCS): static
method ignoreVCSIgnored (line 386) | public function ignoreVCSIgnored(bool $ignoreVCSIgnored): static
method addVCSPattern (line 404) | public static function addVCSPattern(string|array $pattern): void
method sort (line 424) | public function sort(\Closure $closure): static
method sortByExtension (line 440) | public function sortByExtension(): static
method sortByName (line 456) | public function sortByName(bool $useNaturalSort = false): static
method sortByCaseInsensitiveName (line 472) | public function sortByCaseInsensitiveName(bool $useNaturalSort = false...
method sortBySize (line 488) | public function sortBySize(): static
method sortByType (line 504) | public function sortByType(): static
method sortByAccessedTime (line 522) | public function sortByAccessedTime(): static
method reverseSorting (line 534) | public function reverseSorting(): static
method sortByChangedTime (line 554) | public function sortByChangedTime(): static
method sortByModifiedTime (line 572) | public function sortByModifiedTime(): static
method filter (line 592) | public function filter(\Closure $closure, bool $prune = false): static
method followLinks (line 608) | public function followLinks(): static
method useUnixPaths (line 620) | public function useUnixPaths(): static
method ignoreUnreadableDirs (line 634) | public function ignoreUnreadableDirs(bool $ignore = true): static
method in (line 650) | public function in(string|array $dirs): static
method getIterator (line 679) | public function getIterator(): \Iterator
method append (line 726) | public function append(iterable $iterator): static
method hasResults (line 736) | public function hasResults(): bool
method count (line 748) | public function count(): int
method searchInDirectory (line 753) | private function searchInDirectory(string $dir): \Iterator
method normalizeDir (line 854) | private function normalizeDir(string $dir): string
FILE: Gitignore.php
class Gitignore (line 20) | class Gitignore
method toRegex (line 27) | public static function toRegex(string $gitignoreFileContent): string
method toRegexMatchingNegatedPatterns (line 32) | public static function toRegexMatchingNegatedPatterns(string $gitignor...
method buildRegex (line 37) | private static function buildRegex(string $gitignoreFileContent, bool ...
method lineToRegex (line 65) | private static function lineToRegex(string $gitignoreLine): string
FILE: Glob.php
class Glob (line 36) | class Glob
method toRegex (line 41) | public static function toRegex(string $glob, bool $strictLeadingDot = ...
FILE: Iterator/CustomFilterIterator.php
class CustomFilterIterator (line 24) | class CustomFilterIterator extends \FilterIterator
method __construct (line 34) | public function __construct(\Iterator $iterator, array $filters)
method accept (line 49) | public function accept(): bool
FILE: Iterator/DateRangeFilterIterator.php
class DateRangeFilterIterator (line 23) | class DateRangeFilterIterator extends \FilterIterator
method __construct (line 31) | public function __construct(\Iterator $iterator, array $comparators)
method accept (line 41) | public function accept(): bool
FILE: Iterator/DepthRangeFilterIterator.php
class DepthRangeFilterIterator (line 24) | class DepthRangeFilterIterator extends \FilterIterator
method __construct (line 33) | public function __construct(\RecursiveIteratorIterator $iterator, int ...
method accept (line 44) | public function accept(): bool
FILE: Iterator/ExcludeDirectoryFilterIterator.php
class ExcludeDirectoryFilterIterator (line 25) | class ExcludeDirectoryFilterIterator extends \FilterIterator implements ...
method __construct (line 40) | public function __construct(\Iterator $iterator, array $directories)
method accept (line 73) | public function accept(): bool
method hasChildren (line 97) | public function hasChildren(): bool
method getChildren (line 102) | public function getChildren(): self
FILE: Iterator/FileTypeFilterIterator.php
class FileTypeFilterIterator (line 21) | class FileTypeFilterIterator extends \FilterIterator
method __construct (line 30) | public function __construct(
method accept (line 40) | public function accept(): bool
FILE: Iterator/FilecontentFilterIterator.php
class FilecontentFilterIterator (line 24) | class FilecontentFilterIterator extends MultiplePcreFilterIterator
method accept (line 29) | public function accept(): bool
method toRegex (line 54) | protected function toRegex(string $str): string
FILE: Iterator/FilenameFilterIterator.php
class FilenameFilterIterator (line 23) | class FilenameFilterIterator extends MultiplePcreFilterIterator
method accept (line 28) | public function accept(): bool
method toRegex (line 41) | protected function toRegex(string $str): string
FILE: Iterator/LazyIterator.php
class LazyIterator (line 19) | class LazyIterator implements \IteratorAggregate
method __construct (line 23) | public function __construct(callable $iteratorFactory)
method getIterator (line 28) | public function getIterator(): \Traversable
FILE: Iterator/MultiplePcreFilterIterator.php
class MultiplePcreFilterIterator (line 24) | abstract class MultiplePcreFilterIterator extends \FilterIterator
method __construct (line 34) | public function __construct(\Iterator $iterator, array $matchPatterns,...
method isAccepted (line 54) | protected function isAccepted(string $string): bool
method isRegex (line 81) | protected function isRegex(string $str): bool
method toRegex (line 106) | abstract protected function toRegex(string $str): string;
FILE: Iterator/PathFilterIterator.php
class PathFilterIterator (line 24) | class PathFilterIterator extends MultiplePcreFilterIterator
method accept (line 29) | public function accept(): bool
method toRegex (line 52) | protected function toRegex(string $str): string
FILE: Iterator/RecursiveDirectoryIterator.php
class RecursiveDirectoryIterator (line 24) | class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
method __construct (line 37) | public function __construct(string $path, int $flags, bool $ignoreUnre...
method current (line 54) | public function current(): SplFileInfo
method hasChildren (line 75) | public function hasChildren(bool $allowLinks = false): bool
method getChildren (line 96) | public function getChildren(): \RecursiveDirectoryIterator
method next (line 115) | public function next(): void
method rewind (line 122) | public function rewind(): void
FILE: Iterator/SizeRangeFilterIterator.php
class SizeRangeFilterIterator (line 23) | class SizeRangeFilterIterator extends \FilterIterator
method __construct (line 31) | public function __construct(\Iterator $iterator, array $comparators)
method accept (line 41) | public function accept(): bool
FILE: Iterator/SortableIterator.php
class SortableIterator (line 21) | class SortableIterator implements \IteratorAggregate
method __construct (line 45) | public function __construct(\Traversable $iterator, int|callable $sort...
method getIterator (line 87) | public function getIterator(): \Traversable
FILE: Iterator/VcsIgnoredFilterIterator.php
class VcsIgnoredFilterIterator (line 19) | final class VcsIgnoredFilterIterator extends \FilterIterator
method __construct (line 36) | public function __construct(\Iterator $iterator, string $baseDir)
method accept (line 50) | public function accept(): bool
method isIgnored (line 59) | private function isIgnored(string $fileRealPath): bool
method parentDirectoriesUpwards (line 102) | private function parentDirectoriesUpwards(string $from): array
method parentDirectoriesUpTo (line 122) | private function parentDirectoriesUpTo(string $from, string $upTo): array
method parentDirectoriesDownwards (line 133) | private function parentDirectoriesDownwards(string $fileRealPath): array
method readGitignoreFile (line 143) | private function readGitignoreFile(string $path): ?array
method normalizePath (line 165) | private function normalizePath(string $path): string
FILE: SplFileInfo.php
class SplFileInfo (line 19) | class SplFileInfo extends \SplFileInfo
method __construct (line 26) | public function __construct(
method getRelativePath (line 39) | public function getRelativePath(): string
method getRelativePathname (line 49) | public function getRelativePathname(): string
method getFilenameWithoutExtension (line 54) | public function getFilenameWithoutExtension(): string
method getContents (line 66) | public function getContents(): string
FILE: Tests/Comparator/ComparatorTest.php
class ComparatorTest (line 18) | class ComparatorTest extends TestCase
method testInvalidOperator (line 20) | public function testInvalidOperator()
method testTestSucceeds (line 28) | #[DataProvider('provideMatches')]
method provideMatches (line 39) | public static function provideMatches(): array
method testTestFails (line 55) | #[DataProvider('provideNonMatches')]
method provideNonMatches (line 63) | public static function provideNonMatches(): array
FILE: Tests/Comparator/DateComparatorTest.php
class DateComparatorTest (line 18) | class DateComparatorTest extends TestCase
method testConstructor (line 20) | public function testConstructor()
method testTest (line 37) | #[DataProvider('getTestData')]
method getTestData (line 51) | public static function getTestData()
FILE: Tests/Comparator/NumberComparatorTest.php
class NumberComparatorTest (line 18) | class NumberComparatorTest extends TestCase
method testConstructor (line 20) | #[DataProvider('getConstructorTestData')]
method testTest (line 37) | #[DataProvider('getTestData')]
method getTestData (line 51) | public static function getTestData()
method getConstructorTestData (line 81) | public static function getConstructorTestData()
FILE: Tests/FinderOpenBasedirTest.php
class FinderOpenBasedirTest (line 17) | class FinderOpenBasedirTest extends Iterator\RealIteratorTestCase
method testIgnoreVCSIgnoredWithOpenBasedir (line 19) | #[RunInSeparateProcess]
method buildFinder (line 58) | protected function buildFinder()
FILE: Tests/FinderTest.php
class FinderTest (line 20) | class FinderTest extends Iterator\RealIteratorTestCase
method testCreate (line 24) | public function testCreate()
method testDirectories (line 29) | public function testDirectories()
method testFiles (line 42) | public function testFiles()
method testRemoveTrailingSlash (line 83) | public function testRemoveTrailingSlash()
method testSymlinksNotResolved (line 108) | public function testSymlinksNotResolved()
method testBackPathNotNormalized (line 128) | public function testBackPathNotNormalized()
method testDepth (line 137) | public function testDepth()
method testDepthWithArrayParam (line 188) | public function testDepthWithArrayParam()
method testName (line 199) | public function testName()
method testNameWithArrayParam (line 243) | public function testNameWithArrayParam()
method testNotName (line 250) | public function testNotName()
method testNotNameWithArrayParam (line 290) | public function testNotNameWithArrayParam()
method testRegexName (line 303) | #[DataProvider('getRegexNameTestData')]
method testSize (line 314) | public function testSize()
method testSizeWithArrayParam (line 321) | public function testSizeWithArrayParam()
method testDate (line 328) | public function testDate()
method testDateWithArrayParam (line 335) | public function testDateWithArrayParam()
method testExclude (line 342) | public function testExclude()
method testIgnoreVCS (line 365) | public function testIgnoreVCS()
method testIgnoreVCSIgnored (line 450) | public function testIgnoreVCSIgnored()
method testIgnoreVCSIgnoredUpToFirstGitRepositoryRoot (line 468) | public function testIgnoreVCSIgnoredUpToFirstGitRepositoryRoot()
method testIgnoreVCSCanBeDisabledAfterFirstIteration (line 488) | public function testIgnoreVCSCanBeDisabledAfterFirstIteration()
method testIgnoreDotFiles (line 546) | public function testIgnoreDotFiles()
method testIgnoreDotFilesCanBeDisabledAfterFirstIteration (line 627) | public function testIgnoreDotFilesCanBeDisabledAfterFirstIteration()
method testSortByName (line 678) | public function testSortByName()
method testSortByType (line 703) | public function testSortByType()
method testSortByAccessedTime (line 728) | public function testSortByAccessedTime()
method testSortByChangedTime (line 753) | public function testSortByChangedTime()
method testSortByModifiedTime (line 778) | public function testSortByModifiedTime()
method testReverseSorting (line 803) | public function testReverseSorting()
method testSortByNameNatural (line 829) | public function testSortByNameNatural()
method testSortByNameCaseInsensitive (line 876) | public function testSortByNameCaseInsensitive()
method testSort (line 932) | public function testSort()
method testSortAcrossDirectories (line 957) | public function testSortAcrossDirectories()
method testFilter (line 988) | public function testFilter()
method testFilterPrune (line 995) | public function testFilterPrune()
method testFollowLinks (line 1061) | public function testFollowLinks()
method testUseUnixPaths (line 1090) | public function testUseUnixPaths()
method testIn (line 1104) | public function testIn()
method testInWithNonExistentDirectory (line 1128) | public function testInWithNonExistentDirectory()
method testInWithNonExistentDirectoryLegacyException (line 1135) | public function testInWithNonExistentDirectoryLegacyException()
method testInWithGlob (line 1142) | public function testInWithGlob()
method testInWithNonDirectoryGlob (line 1150) | public function testInWithNonDirectoryGlob()
method testInWithGlobBrace (line 1157) | public function testInWithGlobBrace()
method testGetIteratorWithoutIn (line 1169) | public function testGetIteratorWithoutIn()
method testGetIterator (line 1176) | public function testGetIterator()
method testRelativePath (line 1201) | public function testRelativePath()
method testRelativePathname (line 1219) | public function testRelativePathname()
method testGetFilenameWithoutExtension (line 1255) | public function testGetFilenameWithoutExtension()
method testAppendWithAFinder (line 1291) | public function testAppendWithAFinder()
method testAppendWithAnArray (line 1304) | public function testAppendWithAnArray()
method testAppendStandardizesItemsToBeSymfonySplFileInfo (line 1314) | public function testAppendStandardizesItemsToBeSymfonySplFileInfo()
method testRelativePathWithoutAppend (line 1331) | public function testRelativePathWithoutAppend()
method testRelativePathWithAppendedFinderForParentDirectory (line 1363) | public function testRelativePathWithAppendedFinderForParentDirectory()
method testRelativePathWithAppendedFinderForChildDirectory (line 1408) | public function testRelativePathWithAppendedFinderForChildDirectory()
method testRelativePathWithAppendedPaths (line 1447) | public function testRelativePathWithAppendedPaths()
method testRelativePathWithAppendedOnEmptyFinder (line 1486) | public function testRelativePathWithAppendedOnEmptyFinder()
method testAppendReturnsAFinder (line 1515) | public function testAppendReturnsAFinder()
method testAppendEmptyIterableAllowsIteration (line 1520) | public function testAppendEmptyIterableAllowsIteration()
method testAppendDoesNotRequireIn (line 1527) | public function testAppendDoesNotRequireIn()
method testMultipleAppendCallsWithSorting (line 1537) | public function testMultipleAppendCallsWithSorting()
method testCountDirectories (line 1548) | public function testCountDirectories()
method testCountFiles (line 1560) | public function testCountFiles()
method testCountWithoutIn (line 1572) | public function testCountWithoutIn()
method testHasResults (line 1579) | public function testHasResults()
method testNoResults (line 1586) | public function testNoResults()
method testContains (line 1593) | #[DataProvider('getContainsTestData')]
method testContainsOnDirectory (line 1605) | public function testContainsOnDirectory()
method testNotContainsOnDirectory (line 1615) | public function testNotContainsOnDirectory()
method testMultipleLocations (line 1631) | public function testMultipleLocations()
method testMultipleLocationsWithSubDirectories (line 1656) | public function testMultipleLocationsWithSubDirectories()
method testIteratorKeys (line 1678) | public function testIteratorKeys()
method testRegexSpecialCharsLocationWithPathRestrictionContainingStartFlag (line 1686) | public function testRegexSpecialCharsLocationWithPathRestrictionContai...
method getContainsTestData (line 1696) | public static function getContainsTestData()
method getRegexNameTestData (line 1714) | public static function getRegexNameTestData()
method testPath (line 1722) | #[DataProvider('getTestPathData')]
method getTestPathData (line 1733) | public static function getTestPathData()
method testAccessDeniedException (line 1805) | public function testAccessDeniedException()
method testIgnoredAccessDeniedException (line 1841) | public function testIgnoredAccessDeniedException()
method buildFinder (line 1882) | protected function buildFinder()
method formatForAssert (line 1887) | private static function formatForAssert(Finder $finder): array
FILE: Tests/GitignoreTest.php
class GitignoreTest (line 21) | class GitignoreTest extends TestCase
method testToRegex (line 23) | #[DataProvider('provider')]
method provider (line 57) | public static function provider(): array
method providerExtended (line 394) | public static function providerExtended(): array
method testToRegexMatchingNegatedPatterns (line 446) | #[DataProvider('provideNegatedPatternsCases')]
method provideNegatedPatternsCases (line 479) | public static function provideNegatedPatternsCases(): iterable
FILE: Tests/GlobTest.php
class GlobTest (line 18) | class GlobTest extends TestCase
method testGlobToRegexDelimiters (line 20) | public function testGlobToRegexDelimiters()
method testGlobToRegexDoubleStarStrictDots (line 28) | public function testGlobToRegexDoubleStarStrictDots()
method testGlobToRegexDoubleStarNonStrictDots (line 45) | public function testGlobToRegexDoubleStarNonStrictDots()
method testGlobToRegexDoubleStarWithoutLeadingSlash (line 62) | public function testGlobToRegexDoubleStarWithoutLeadingSlash()
method testGlobToRegexDoubleStarWithoutLeadingSlashNotStrictLeadingDot (line 79) | public function testGlobToRegexDoubleStarWithoutLeadingSlashNotStrictL...
method testGlobToRegexDoubleStarMatchesRootFiles (line 96) | public function testGlobToRegexDoubleStarMatchesRootFiles()
FILE: Tests/Iterator/CustomFilterIteratorTest.php
class CustomFilterIteratorTest (line 17) | class CustomFilterIteratorTest extends IteratorTestCase
method testWithInvalidFilter (line 19) | public function testWithInvalidFilter()
method testAccept (line 25) | #[DataProvider('getAcceptData')]
method getAcceptData (line 35) | public static function getAcceptData()
FILE: Tests/Iterator/DateRangeFilterIteratorTest.php
class DateRangeFilterIteratorTest (line 18) | class DateRangeFilterIteratorTest extends RealIteratorTestCase
method testAccept (line 20) | #[DataProvider('getAcceptData')]
method getAcceptData (line 32) | public static function getAcceptData()
FILE: Tests/Iterator/DepthRangeFilterIteratorTest.php
class DepthRangeFilterIteratorTest (line 17) | class DepthRangeFilterIteratorTest extends RealIteratorTestCase
method testAccept (line 19) | #[DataProvider('getAcceptData')]
method getAcceptData (line 32) | public static function getAcceptData()
FILE: Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php
class ExcludeDirectoryFilterIteratorTest (line 18) | class ExcludeDirectoryFilterIteratorTest extends RealIteratorTestCase
method testAccept (line 20) | #[DataProvider('getAcceptData')]
method getAcceptData (line 30) | public static function getAcceptData()
FILE: Tests/Iterator/FileTypeFilterIteratorTest.php
class FileTypeFilterIteratorTest (line 17) | class FileTypeFilterIteratorTest extends RealIteratorTestCase
method testAccept (line 19) | #[DataProvider('getAcceptData')]
method getAcceptData (line 29) | public static function getAcceptData()
class InnerTypeIterator (line 67) | class InnerTypeIterator extends \ArrayIterator
method current (line 69) | public function current(): \SplFileInfo
method isFile (line 74) | public function isFile(): bool
method isDir (line 79) | public function isDir(): bool
FILE: Tests/Iterator/FilecontentFilterIteratorTest.php
class FilecontentFilterIteratorTest (line 17) | class FilecontentFilterIteratorTest extends IteratorTestCase
method testAccept (line 19) | public function testAccept()
method testDirectory (line 26) | public function testDirectory()
method testUnreadableFile (line 33) | public function testUnreadableFile()
method testFilter (line 40) | #[DataProvider('getTestFilterData')]
method getTestFilterData (line 47) | public static function getTestFilterData()
FILE: Tests/Iterator/FilenameFilterIteratorTest.php
class FilenameFilterIteratorTest (line 17) | class FilenameFilterIteratorTest extends IteratorTestCase
method testAccept (line 19) | #[DataProvider('getAcceptData')]
method getAcceptData (line 29) | public static function getAcceptData()
FILE: Tests/Iterator/InnerNameIterator.php
class InnerNameIterator (line 14) | class InnerNameIterator extends \ArrayIterator
method current (line 16) | public function current(): \SplFileInfo
method getFilename (line 21) | public function getFilename()
FILE: Tests/Iterator/Iterator.php
class Iterator (line 14) | class Iterator implements \Iterator
method __construct (line 18) | public function __construct(array $values = [])
method attach (line 26) | public function attach(\SplFileInfo $fileinfo): void
method rewind (line 31) | public function rewind(): void
method valid (line 36) | public function valid(): bool
method next (line 41) | public function next(): void
method current (line 46) | public function current(): mixed
method key (line 51) | public function key(): mixed
FILE: Tests/Iterator/IteratorTestCase.php
class IteratorTestCase (line 16) | abstract class IteratorTestCase extends TestCase
method assertIterator (line 18) | protected function assertIterator($expected, \Traversable $iterator)
method assertOrderedIterator (line 32) | protected function assertOrderedIterator($expected, \Traversable $iter...
method assertOrderedIteratorForGroups (line 49) | protected function assertOrderedIteratorForGroups(array $expected, \Tr...
method assertIteratorInForeach (line 67) | protected function assertIteratorInForeach(array $expected, \Traversab...
method assertOrderedIteratorInForeach (line 84) | protected function assertOrderedIteratorInForeach(array $expected, \Tr...
FILE: Tests/Iterator/LazyIteratorTest.php
class LazyIteratorTest (line 17) | class LazyIteratorTest extends TestCase
method testLazy (line 19) | public function testLazy()
method testDelegate (line 28) | public function testDelegate()
method testInnerDestructedAtTheEnd (line 35) | public function testInnerDestructedAtTheEnd()
FILE: Tests/Iterator/MockFileListIterator.php
class MockFileListIterator (line 14) | class MockFileListIterator extends \ArrayIterator
method __construct (line 16) | public function __construct(array $filesArray = [])
FILE: Tests/Iterator/MockSplFileInfo.php
class MockSplFileInfo (line 14) | class MockSplFileInfo extends \SplFileInfo
method __construct (line 26) | public function __construct($param)
method isFile (line 51) | public function isFile(): bool
method isDir (line 60) | public function isDir(): bool
method isReadable (line 69) | public function isReadable(): bool
method getContents (line 74) | public function getContents()
method setContents (line 79) | public function setContents($contents)
method setMode (line 84) | public function setMode($mode)
method setType (line 89) | public function setType($type)
method setRelativePath (line 104) | public function setRelativePath($relativePath)
method setRelativePathname (line 109) | public function setRelativePathname($relativePathname)
method getRelativePath (line 114) | public function getRelativePath()
method getRelativePathname (line 119) | public function getRelativePathname()
FILE: Tests/Iterator/MultiplePcreFilterIteratorTest.php
class MultiplePcreFilterIteratorTest (line 18) | class MultiplePcreFilterIteratorTest extends TestCase
method testIsRegex (line 20) | #[DataProvider('getIsRegexFixtures')]
method getIsRegexFixtures (line 27) | public static function getIsRegexFixtures()
class TestMultiplePcreFilterIterator (line 49) | class TestMultiplePcreFilterIterator extends MultiplePcreFilterIterator
method __construct (line 51) | public function __construct()
method accept (line 55) | public function accept(): bool
method isRegex (line 60) | public function isRegex(string $str): bool
method toRegex (line 65) | public function toRegex(string $str): string
FILE: Tests/Iterator/PathFilterIteratorTest.php
class PathFilterIteratorTest (line 17) | class PathFilterIteratorTest extends IteratorTestCase
method testFilter (line 19) | #[DataProvider('getTestFilterData')]
method getTestFilterData (line 26) | public static function getTestFilterData()
FILE: Tests/Iterator/RealIteratorTestCase.php
class RealIteratorTestCase (line 17) | abstract class RealIteratorTestCase extends IteratorTestCase
method setUpBeforeClass (line 22) | public static function setUpBeforeClass(): void
method tearDownAfterClass (line 99) | public static function tearDownAfterClass(): void
method toAbsolute (line 104) | protected static function toAbsolute($files = null)
method toAbsoluteFixtures (line 131) | protected static function toAbsoluteFixtures($files)
FILE: Tests/Iterator/RecursiveDirectoryIteratorTest.php
class RecursiveDirectoryIteratorTest (line 17) | class RecursiveDirectoryIteratorTest extends IteratorTestCase
method setUp (line 19) | protected function setUp(): void
method testRewindOnFtp (line 26) | #[Group('network')]
method testSeekOnFtp (line 41) | #[Group('network')]
method testTrailingDirectorySeparatorIsStripped (line 68) | public function testTrailingDirectorySeparatorIsStripped()
FILE: Tests/Iterator/SizeRangeFilterIteratorTest.php
class SizeRangeFilterIteratorTest (line 18) | class SizeRangeFilterIteratorTest extends RealIteratorTestCase
method testAccept (line 20) | #[DataProvider('getAcceptData')]
method getAcceptData (line 30) | public static function getAcceptData()
class InnerSizeIterator (line 48) | class InnerSizeIterator extends \ArrayIterator
method current (line 50) | public function current(): \SplFileInfo
method getFilename (line 55) | public function getFilename(): string
method isFile (line 60) | public function isFile(): bool
method getSize (line 65) | public function getSize(): int
FILE: Tests/Iterator/SortableIteratorTest.php
class SortableIteratorTest (line 17) | class SortableIteratorTest extends RealIteratorTestCase
method testConstructor (line 19) | public function testConstructor()
method testAccept (line 29) | #[DataProvider('getAcceptData')]
method getAcceptData (line 70) | public static function getAcceptData()
FILE: Tests/Iterator/VcsIgnoredFilterIteratorTest.php
class VcsIgnoredFilterIteratorTest (line 18) | class VcsIgnoredFilterIteratorTest extends IteratorTestCase
method setUp (line 22) | protected function setUp(): void
method tearDown (line 28) | protected function tearDown(): void
method testAccept (line 36) | #[DataProvider('getAcceptData')]
method getAcceptData (line 60) | public static function getAcceptData(): iterable
method testAcceptAtRootDirectory (line 366) | public function testAcceptAtRootDirectory()
method toAbsolute (line 375) | private function toAbsolute(array $files): array
method removeDirectory (line 384) | private function removeDirectory(string $dir): void
FILE: Tests/Iterator/VfsIteratorTestTrait.php
type VfsIteratorTestTrait (line 14) | trait VfsIteratorTestTrait
method setUp (line 26) | protected function setUp(): void
method tearDown (line 119) | protected function tearDown(): void
method setupVfsProvider (line 129) | protected function setupVfsProvider(array $data): void
method stripSchemeFromVfsPath (line 159) | protected function stripSchemeFromVfsPath(string $url): string
method assertSameVfsIterator (line 169) | protected function assertSameVfsIterator(array $expected, \Traversable...
Condensed preview — 85 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (258K chars).
[
{
"path": ".gitattributes",
"chars": 74,
"preview": "/Tests export-ignore\n/phpunit.xml.dist export-ignore\n/.git* export-ignore\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 278,
"preview": "Please do not submit any Pull Requests here. They will be closed.\n---\n\nPlease submit your PR here instead:\nhttps://githu"
},
{
"path": ".github/workflows/close-pull-request.yml",
"chars": 544,
"preview": "name: Close Pull Request\n\non:\n pull_request_target:\n types: [opened]\n\njobs:\n run:\n runs-on: ubuntu-latest\n st"
},
{
"path": ".gitignore",
"chars": 34,
"preview": "vendor/\ncomposer.lock\nphpunit.xml\n"
},
{
"path": "CHANGELOG.md",
"chars": 2566,
"preview": "CHANGELOG\n=========\n\n6.4\n---\n\n * Add early directory pruning to `Finder::filter()`\n\n6.2\n---\n\n * Add `Finder::sortByExten"
},
{
"path": "Comparator/Comparator.php",
"chars": 1420,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Comparator/DateComparator.php",
"chars": 1408,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Comparator/NumberComparator.php",
"chars": 2566,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Exception/AccessDeniedException.php",
"chars": 426,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Exception/DirectoryNotFoundException.php",
"chars": 419,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Finder.php",
"chars": 24593,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Gitignore.php",
"chars": 3016,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Glob.php",
"chars": 3865,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/CustomFilterIterator.php",
"chars": 1527,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/DateRangeFilterIterator.php",
"chars": 1401,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/DepthRangeFilterIterator.php",
"chars": 1378,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/ExcludeDirectoryFilterIterator.php",
"chars": 3382,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/FileTypeFilterIterator.php",
"chars": 1364,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/FilecontentFilterIterator.php",
"chars": 1417,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/FilenameFilterIterator.php",
"chars": 1105,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/LazyIterator.php",
"chars": 683,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/MultiplePcreFilterIterator.php",
"chars": 3146,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/PathFilterIterator.php",
"chars": 1422,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/RecursiveDirectoryIterator.php",
"chars": 4136,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/SizeRangeFilterIterator.php",
"chars": 1360,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/SortableIterator.php",
"chars": 4894,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Iterator/VcsIgnoredFilterIterator.php",
"chars": 4718,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "Copyright (c) 2004-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 495,
"preview": "Finder Component\n================\n\nThe Finder component finds files and directories via an intuitive fluent\ninterface.\n\n"
},
{
"path": "SplFileInfo.php",
"chars": 1886,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Comparator/ComparatorTest.php",
"chars": 2084,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Comparator/DateComparatorTest.php",
"chars": 2472,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Comparator/NumberComparatorTest.php",
"chars": 3406,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/FinderOpenBasedirTest.php",
"chars": 1907,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/FinderTest.php",
"chars": 61544,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Fixtures/.dot/a",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/.dot/b/c.neon",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/.dot/b/d.neon",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/copy/A/B/C/abc.dat.copy",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/copy/A/B/ab.dat.copy",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/copy/A/a.dat.copy",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/dolor.txt",
"chars": 29,
"preview": "dolor sit amet\nDOLOR SIT AMET"
},
{
"path": "Tests/Fixtures/gitignore/.gitignore",
"chars": 6,
"preview": "c.txt\n"
},
{
"path": "Tests/Fixtures/gitignore/git_root/search_root/.gitignore",
"chars": 7,
"preview": "/a.txt\n"
},
{
"path": "Tests/Fixtures/gitignore/git_root/search_root/b.txt",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/gitignore/git_root/search_root/dir/.gitignore",
"chars": 7,
"preview": "/b.txt\n"
},
{
"path": "Tests/Fixtures/gitignore/git_root/search_root/dir/a.txt",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/gitignore/search_root/.gitignore",
"chars": 7,
"preview": "/a.txt\n"
},
{
"path": "Tests/Fixtures/gitignore/search_root/a.txt",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/gitignore/search_root/b.txt",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/gitignore/search_root/dir/.gitignore",
"chars": 7,
"preview": "/b.txt\n"
},
{
"path": "Tests/Fixtures/gitignore/search_root/dir/a.txt",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/gitignore/search_root/dir/b.txt",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/ipsum.txt",
"chars": 41,
"preview": "ipsum dolor sit amet\nIPSUM DOLOR SIT AMET"
},
{
"path": "Tests/Fixtures/lorem.txt",
"chars": 53,
"preview": "lorem ipsum dolor sit amet\nLOREM IPSUM DOLOR SIT AMET"
},
{
"path": "Tests/Fixtures/one/.dot",
"chars": 4,
"preview": ".dot"
},
{
"path": "Tests/Fixtures/one/a",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/one/b/c.neon",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/one/b/d.neon",
"chars": 0,
"preview": ""
},
{
"path": "Tests/Fixtures/with space/foo.txt",
"chars": 0,
"preview": ""
},
{
"path": "Tests/GitignoreTest.php",
"chars": 26610,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/GlobTest.php",
"chars": 4031,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/CustomFilterIteratorTest.php",
"chars": 1221,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/DateRangeFilterIteratorTest.php",
"chars": 2507,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/DepthRangeFilterIteratorTest.php",
"chars": 2794,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/ExcludeDirectoryFilterIteratorTest.php",
"chars": 2907,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/FileTypeFilterIteratorTest.php",
"chars": 1983,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/FilecontentFilterIteratorTest.php",
"chars": 2566,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/FilenameFilterIteratorTest.php",
"chars": 1211,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/InnerNameIterator.php",
"chars": 530,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/Iterator.php",
"chars": 1075,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/IteratorTestCase.php",
"chars": 3425,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/LazyIteratorTest.php",
"chars": 1235,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/MockFileListIterator.php",
"chars": 539,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/MockSplFileInfo.php",
"chars": 3169,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/MultiplePcreFilterIteratorTest.php",
"chars": 2380,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/PathFilterIteratorTest.php",
"chars": 3052,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/RealIteratorTestCase.php",
"chars": 4135,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/RecursiveDirectoryIteratorTest.php",
"chars": 2726,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/SizeRangeFilterIteratorTest.php",
"chars": 1633,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/SortableIteratorTest.php",
"chars": 8180,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/VcsIgnoredFilterIteratorTest.php",
"chars": 10071,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "Tests/Iterator/VfsIteratorTestTrait.php",
"chars": 5312,
"preview": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the ful"
},
{
"path": "composer.json",
"chars": 766,
"preview": "{\n \"name\": \"symfony/finder\",\n \"type\": \"library\",\n \"description\": \"Finds files and directories via an intuitive "
},
{
"path": "phpunit.xml.dist",
"chars": 994,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:noNa"
}
]
About this extraction
This page contains the full source code of the symfony/finder GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 85 files (237.5 KB), approximately 62.6k tokens, and a symbol index with 345 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.