PHP 8 Compatibility

This commit is contained in:
Frédéric Guillot
2022-02-05 11:49:03 -08:00
committed by GitHub
parent 61e63ef9e0
commit f5bb55bdb8
558 changed files with 6262 additions and 21691 deletions

View File

@@ -1,6 +1,14 @@
CHANGELOG
=========
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
-----

View File

@@ -12,8 +12,6 @@
namespace Symfony\Component\Finder\Comparator;
/**
* Comparator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Comparator
@@ -21,25 +19,44 @@ class Comparator
private $target;
private $operator = '==';
public function __construct(string $target = null, string $operator = '==')
{
if (null === $target) {
trigger_deprecation('symfony/finder', '5.4', 'Constructing a "%s" without setting "$target" is deprecated.', __CLASS__);
}
$this->target = $target;
$this->doSetOperator($operator);
}
/**
* Gets the target value.
*
* @return string The target value
* @return string
*/
public function getTarget()
{
if (null === $this->target) {
trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__);
}
return $this->target;
}
/**
* @deprecated set the target via the constructor instead
*/
public function setTarget(string $target)
{
trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the target via the constructor instead.', __METHOD__);
$this->target = $target;
}
/**
* Gets the comparison operator.
*
* @return string The operator
* @return string
*/
public function getOperator()
{
@@ -50,18 +67,14 @@ class Comparator
* Sets the comparison operator.
*
* @throws \InvalidArgumentException
*
* @deprecated set the operator via the constructor instead
*/
public function setOperator(string $operator)
{
if ('' === $operator) {
$operator = '==';
}
trigger_deprecation('symfony/finder', '5.4', '"%s" is deprecated. Set the operator via the constructor instead.', __METHOD__);
if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
$this->doSetOperator('' === $operator ? '==' : $operator);
}
/**
@@ -73,6 +86,10 @@ class Comparator
*/
public function test($test)
{
if (null === $this->target) {
trigger_deprecation('symfony/finder', '5.4', 'Calling "%s" without initializing the target is deprecated.', __METHOD__);
}
switch ($this->operator) {
case '>':
return $test > $this->target;
@@ -88,4 +105,13 @@ class Comparator
return $test == $this->target;
}
private function doSetOperator(string $operator): void
{
if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
}
}

View File

@@ -36,7 +36,7 @@ class DateComparator extends Comparator
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = isset($matches[1]) ? $matches[1] : '==';
$operator = $matches[1] ?? '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
@@ -45,7 +45,6 @@ class DateComparator extends Comparator
$operator = '<';
}
$this->setOperator($operator);
$this->setTarget($target);
parent::__construct($target, $operator);
}
}

View File

@@ -41,8 +41,8 @@ class NumberComparator extends Comparator
*/
public function __construct(?string $test)
{
if (!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));
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];
@@ -73,7 +73,6 @@ class NumberComparator extends Comparator
}
}
$this->setTarget($target);
$this->setOperator(isset($matches[1]) ? $matches[1] : '==');
parent::__construct($target, $matches[1] ?: '==');
}
}

View File

@@ -20,6 +20,7 @@ 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;
@@ -35,6 +36,8 @@ use Symfony\Component\Finder\Iterator\SortableIterator;
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @implements \IteratorAggregate<string, SplFileInfo>
*/
class Finder implements \IteratorAggregate, \Countable
{
@@ -581,16 +584,16 @@ class Finder implements \IteratorAggregate, \Countable
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = $this->normalizeDir($dir);
$resolvedDirs[] = [$this->normalizeDir($dir)];
} elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
sort($glob);
$resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $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);
$this->dirs = array_merge($this->dirs, ...$resolvedDirs);
return $this;
}
@@ -600,10 +603,11 @@ class Finder implements \IteratorAggregate, \Countable
*
* This method implements the IteratorAggregate interface.
*
* @return \Iterator|SplFileInfo[] An iterator
* @return \Iterator<string, SplFileInfo>
*
* @throws \LogicException if the in() method has not been called
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
@@ -611,18 +615,30 @@ class Finder implements \IteratorAggregate, \Countable
}
if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
return $this->searchInDirectory($this->dirs[0]);
$iterator = $this->searchInDirectory($this->dirs[0]);
if ($this->sort || $this->reverseSorting) {
$iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
}
return $iterator;
}
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append($this->searchInDirectory($dir));
$iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) {
return $this->searchInDirectory($dir);
})));
}
foreach ($this->iterators as $it) {
$iterator->append($it);
}
if ($this->sort || $this->reverseSorting) {
$iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
}
return $iterator;
}
@@ -641,10 +657,11 @@ class Finder implements \IteratorAggregate, \Countable
$this->iterators[] = $iterator->getIterator();
} elseif ($iterator instanceof \Iterator) {
$this->iterators[] = $iterator;
} elseif ($iterator instanceof \Traversable || \is_array($iterator)) {
} elseif (is_iterable($iterator)) {
$it = new \ArrayIterator();
foreach ($iterator as $file) {
$it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
$file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file);
$it[$file->getPathname()] = $file;
}
$this->iterators[] = $it;
} else {
@@ -673,6 +690,7 @@ class Finder implements \IteratorAggregate, \Countable
*
* @return int
*/
#[\ReturnTypeWillChange]
public function count()
{
return iterator_count($this->getIterator());
@@ -691,14 +709,6 @@ class Finder implements \IteratorAggregate, \Countable
$notPaths[] = '#(^|/)\..+(/|$)#';
}
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
$gitignoreFilePath = sprintf('%s/.gitignore', $dir);
if (!is_readable($gitignoreFilePath)) {
throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
}
$notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
}
$minDepth = 0;
$maxDepth = \PHP_INT_MAX;
@@ -767,9 +777,8 @@ class Finder implements \IteratorAggregate, \Countable
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
}
if ($this->sort || $this->reverseSorting) {
$iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting);
$iterator = $iteratorAggregate->getIterator();
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
$iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
}
return $iterator;

View File

@@ -14,6 +14,7 @@ namespace Symfony\Component\Finder;
/**
* Gitignore matches against text.
*
* @author Michael Voříšek <vorismi3@fel.cvut.cz>
* @author Ahmed Abdou <mail@ahmd.io>
*/
class Gitignore
@@ -21,113 +22,72 @@ class Gitignore
/**
* Returns a regexp which is the equivalent of the gitignore pattern.
*
* @return string The regexp
* Format specification: https://git-scm.com/docs/gitignore#_pattern_format
*/
public static function toRegex(string $gitignoreFileContent): string
{
$gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent);
$gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent);
$positives = [];
$negatives = [];
foreach ($gitignoreLines as $i => $line) {
$line = trim($line);
if ('' === $line) {
continue;
}
if (1 === preg_match('/^!/', $line)) {
$positives[$i] = null;
$negatives[$i] = self::getRegexFromGitignore(preg_replace('/^!(.*)/', '${1}', $line), true);
continue;
}
$negatives[$i] = null;
$positives[$i] = self::getRegexFromGitignore($line);
}
$index = 0;
$patterns = [];
foreach ($positives as $pattern) {
if (null === $pattern) {
continue;
}
$negativesAfter = array_filter(\array_slice($negatives, ++$index));
if ([] !== $negativesAfter) {
$pattern .= sprintf('(?<!%s)', implode('|', $negativesAfter));
}
$patterns[] = $pattern;
}
return sprintf('/^((%s))$/', implode(')|(', $patterns));
return self::buildRegex($gitignoreFileContent, false);
}
private static function getRegexFromGitignore(string $gitignorePattern, bool $negative = false): string
public static function toRegexMatchingNegatedPatterns(string $gitignoreFileContent): string
{
$regex = '';
$isRelativePath = false;
// If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular .gitignore file itself
$slashPosition = strpos($gitignorePattern, '/');
if (false !== $slashPosition && \strlen($gitignorePattern) - 1 !== $slashPosition) {
if (0 === $slashPosition) {
$gitignorePattern = substr($gitignorePattern, 1);
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 ('!' === substr($line, 0, 1)) {
$line = substr($line, 1);
$isNegative = true;
} else {
$isNegative = false;
}
$isRelativePath = true;
$regex .= '^';
}
if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) {
$gitignorePattern = substr($gitignorePattern, 0, -1);
}
$iMax = \strlen($gitignorePattern);
for ($i = 0; $i < $iMax; ++$i) {
$tripleChars = substr($gitignorePattern, $i, 3);
if ('**/' === $tripleChars || '/**' === $tripleChars) {
$regex .= '.*';
$i += 2;
continue;
}
$doubleChars = substr($gitignorePattern, $i, 2);
if ('**' === $doubleChars) {
$regex .= '.*';
++$i;
continue;
}
if ('*/' === $doubleChars) {
$regex .= '[^\/]*\/?[^\/]*';
++$i;
continue;
}
$c = $gitignorePattern[$i];
switch ($c) {
case '*':
$regex .= $isRelativePath ? '[^\/]*' : '[^\/]*\/?[^\/]*';
break;
case '/':
case '.':
case ':':
case '(':
case ')':
case '{':
case '}':
$regex .= '\\'.$c;
break;
default:
$regex .= $c;
if ('' !== $line) {
if ($isNegative xor $inverted) {
$res = '(?!'.self::lineToRegex($line).'$)'.$res;
} else {
$res = '(?:'.$res.'|'.self::lineToRegex($line).')';
}
}
}
if ($negative) {
// a lookbehind assertion has to be a fixed width (it can not have nested '|' statements)
return sprintf('%s$|%s\/$', $regex, $regex);
return '~^(?:'.$res.')~s';
}
private static function lineToRegex(string $gitignoreLine): string
{
if ('' === $gitignoreLine) {
return '$f'; // always false
}
return '(?>'.$regex.'($|\/.*))';
$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('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', function (array $matches): string {
return '['.('' !== $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, '/') ? '(?:$|/)' : '');
}
}

View File

@@ -18,14 +18,16 @@ namespace Symfony\Component\Finder\Iterator;
* to remove files.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
*/
class CustomFilterIterator extends \FilterIterator
{
private $filters = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
* @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
*
* @throws \InvalidArgumentException
*/
@@ -44,8 +46,9 @@ class CustomFilterIterator extends \FilterIterator
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();

View File

@@ -17,14 +17,16 @@ 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 $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param DateComparator[] $comparators An array of DateComparator instances
* @param \Iterator<string, \SplFileInfo> $iterator
* @param DateComparator[] $comparators
*/
public function __construct(\Iterator $iterator, array $comparators)
{
@@ -36,8 +38,9 @@ class DateRangeFilterIterator extends \FilterIterator
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();

View File

@@ -15,15 +15,20 @@ 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 $minDepth = 0;
/**
* @param \RecursiveIteratorIterator $iterator The Iterator to filter
* @param int $minDepth The min depth
* @param int $maxDepth The max depth
* @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)
{
@@ -36,8 +41,9 @@ class DepthRangeFilterIterator extends \FilterIterator
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
return $this->getInnerIterator()->getDepth() >= $this->minDepth;

View File

@@ -15,6 +15,9 @@ namespace Symfony\Component\Finder\Iterator;
* ExcludeDirectoryFilterIterator filters out directories.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @extends \FilterIterator<string, \SplFileInfo>
* @implements \RecursiveIterator<string, \SplFileInfo>
*/
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
@@ -34,7 +37,7 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi
$patterns = [];
foreach ($directories as $directory) {
$directory = rtrim($directory, '/');
if (!$this->isRecursive || false !== strpos($directory, '/')) {
if (!$this->isRecursive || str_contains($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
@@ -50,8 +53,9 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi
/**
* Filters the iterator values.
*
* @return bool True if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
@@ -71,11 +75,16 @@ class ExcludeDirectoryFilterIterator extends \FilterIterator implements \Recursi
/**
* @return bool
*/
#[\ReturnTypeWillChange]
public function hasChildren()
{
return $this->isRecursive && $this->iterator->hasChildren();
}
/**
* @return self
*/
#[\ReturnTypeWillChange]
public function getChildren()
{
$children = new self($this->iterator->getChildren(), []);

View File

@@ -15,6 +15,8 @@ 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
{
@@ -37,8 +39,9 @@ class FileTypeFilterIterator extends \FilterIterator
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();

View File

@@ -16,14 +16,17 @@ namespace Symfony\Component\Finder\Iterator;
*
* @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.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
if (!$this->matchRegexps && !$this->noMatchRegexps) {
@@ -49,7 +52,7 @@ class FilecontentFilterIterator extends MultiplePcreFilterIterator
*
* @param string $str Pattern: string or regexp
*
* @return string regexp corresponding to a given string or regexp
* @return string
*/
protected function toRegex(string $str)
{

View File

@@ -17,14 +17,17 @@ 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.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
return $this->isAccepted($this->current()->getFilename());
@@ -38,7 +41,7 @@ class FilenameFilterIterator extends MultiplePcreFilterIterator
*
* @param string $str Pattern: glob or regexp
*
* @return string regexp corresponding to a given glob or regexp
* @return string
*/
protected function toRegex(string $str)
{

View File

@@ -0,0 +1,32 @@
<?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 $iteratorFactory;
public function __construct(callable $iteratorFactory)
{
$this->iteratorFactory = $iteratorFactory;
}
public function getIterator(): \Traversable
{
yield from ($this->iteratorFactory)();
}
}

View File

@@ -15,6 +15,11 @@ 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
{

View File

@@ -16,14 +16,17 @@ namespace Symfony\Component\Finder\Iterator;
*
* @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.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$filename = $this->current()->getRelativePathname();
@@ -47,7 +50,7 @@ class PathFilterIterator extends MultiplePcreFilterIterator
*
* @param string $str Pattern: regexp or dirname
*
* @return string regexp corresponding to a given string or regexp
* @return string
*/
protected function toRegex(string $str)
{

View File

@@ -56,14 +56,15 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
/**
* Return an instance of SplFileInfo with support for relative paths.
*
* @return SplFileInfo File information
* @return SplFileInfo
*/
#[\ReturnTypeWillChange]
public function current()
{
// the logic here avoids redoing the same work in all iterations
if (null === $subPathname = $this->subPath) {
$subPathname = $this->subPath = (string) $this->getSubPath();
$subPathname = $this->subPath = $this->getSubPath();
}
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
@@ -78,10 +79,35 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
}
/**
* @return \RecursiveIterator
* @param bool $allowLinks
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function hasChildren($allowLinks = false)
{
$hasChildren = parent::hasChildren($allowLinks);
if (!$hasChildren || !$this->ignoreUnreadableDirs) {
return $hasChildren;
}
try {
parent::getChildren();
return true;
} catch (\UnexpectedValueException $e) {
// If directory is unreadable and finder is set to ignore it, skip children
return false;
}
}
/**
* @return \RecursiveDirectoryIterator
*
* @throws AccessDeniedException
*/
#[\ReturnTypeWillChange]
public function getChildren()
{
try {
@@ -98,18 +124,16 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
return $children;
} catch (\UnexpectedValueException $e) {
if ($this->ignoreUnreadableDirs) {
// If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
return new \RecursiveArrayIterator([]);
} else {
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
}
/**
* Do nothing for non rewindable stream.
*
* @return void
*/
#[\ReturnTypeWillChange]
public function rewind()
{
if (false === $this->isRewindable()) {
@@ -122,7 +146,7 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
/**
* Checks if the stream is rewindable.
*
* @return bool true when the stream is rewindable, false otherwise
* @return bool
*/
public function isRewindable()
{

View File

@@ -17,14 +17,16 @@ 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 $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param NumberComparator[] $comparators An array of NumberComparator instances
* @param \Iterator<string, \SplFileInfo> $iterator
* @param NumberComparator[] $comparators
*/
public function __construct(\Iterator $iterator, array $comparators)
{
@@ -36,8 +38,9 @@ class SizeRangeFilterIterator extends \FilterIterator
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
* @return bool
*/
#[\ReturnTypeWillChange]
public function accept()
{
$fileinfo = $this->current();

View File

@@ -15,6 +15,8 @@ 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
{
@@ -30,8 +32,8 @@ class SortableIterator implements \IteratorAggregate
private $sort;
/**
* @param \Traversable $iterator The Iterator to filter
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
* @param \Traversable<string, \SplFileInfo> $iterator
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
*
* @throws \InvalidArgumentException
*/
@@ -80,8 +82,9 @@ class SortableIterator implements \IteratorAggregate
}
/**
* @return \Traversable
* @return \Traversable<string, \SplFileInfo>
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
if (1 === $this->sort) {

View File

@@ -0,0 +1,151 @@
<?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;
final class VcsIgnoredFilterIterator extends \FilterIterator
{
/**
* @var string
*/
private $baseDir;
/**
* @var array<string, array{0: string, 1: string}|null>
*/
private $gitignoreFilesCache = [];
/**
* @var array<string, bool>
*/
private $ignoredPathsCache = [];
public function __construct(\Iterator $iterator, string $baseDir)
{
$this->baseDir = $this->normalizePath($baseDir);
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->parentsDirectoryDownward($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 parentsDirectoryDownward(string $fileRealPath): array
{
$parentDirectories = [];
$parentDirectory = $fileRealPath;
while (true) {
$newParentDirectory = \dirname($parentDirectory);
// dirname('/') = '/'
if ($newParentDirectory === $parentDirectory) {
break;
}
$parentDirectory = $newParentDirectory;
if (0 !== strpos($parentDirectory, $this->baseDir)) {
break;
}
$parentDirectories[] = $parentDirectory;
}
return array_reverse($parentDirectories);
}
/**
* @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;
}
}

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2020 Fabien Potencier
Copyright (c) 2004-2022 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

View File

@@ -7,8 +7,8 @@ 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)
* [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)

View File

@@ -38,7 +38,7 @@ class SplFileInfo extends \SplFileInfo
*
* This path does not contain the file name.
*
* @return string the relative path
* @return string
*/
public function getRelativePath()
{
@@ -50,7 +50,7 @@ class SplFileInfo extends \SplFileInfo
*
* This path contains the file name.
*
* @return string the relative path name
* @return string
*/
public function getRelativePathname()
{
@@ -67,15 +67,18 @@ class SplFileInfo extends \SplFileInfo
/**
* Returns the contents of the file.
*
* @return string the contents of the file
* @return string
*
* @throws \RuntimeException
*/
public function getContents()
{
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$content = file_get_contents($this->getPathname());
restore_error_handler();
try {
$content = file_get_contents($this->getPathname());
} finally {
restore_error_handler();
}
if (false === $content) {
throw new \RuntimeException($error);
}

View File

@@ -1,7 +1,7 @@
{
"name": "symfony/finder",
"type": "library",
"description": "Symfony Finder Component",
"description": "Finds files and directories via an intuitive fluent interface",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
@@ -16,7 +16,9 @@
}
],
"require": {
"php": ">=7.2.5"
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-php80": "^1.16"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Finder\\": "" },