[Finder] Optimize the hot-path

This commit is contained in:
Nicolas Grekas 2015-09-17 14:13:49 +02:00
parent 4a9676b5a2
commit f156de6b71
2 changed files with 50 additions and 13 deletions

View File

@ -18,8 +18,10 @@ namespace Symfony\Component\Finder\Iterator;
*/ */
class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator
{ {
private $iterator;
private $isRecursive; private $isRecursive;
private $patterns; private $excludedDirs = array();
private $excludedPattern;
/** /**
* Constructor. * Constructor.
@ -29,10 +31,18 @@ class ExcludeDirectoryFilterIterator extends FilterIterator implements \Recursiv
*/ */
public function __construct(\Iterator $iterator, array $directories) public function __construct(\Iterator $iterator, array $directories)
{ {
$this->iterator = $iterator;
$this->isRecursive = $iterator instanceof \RecursiveIterator; $this->isRecursive = $iterator instanceof \RecursiveIterator;
$this->patterns = array(); $patterns = array();
foreach ($directories as $directory) { foreach ($directories as $directory) {
$this->patterns[] = '#(^|/)'.preg_quote($directory, '#').'(/|$)#'; if (!$this->isRecursive || false !== strpos($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
}
}
if ($patterns) {
$this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
} }
parent::__construct($iterator); parent::__construct($iterator);
@ -45,12 +55,15 @@ class ExcludeDirectoryFilterIterator extends FilterIterator implements \Recursiv
*/ */
public function accept() public function accept()
{ {
$path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
$path = str_replace('\\', '/', $path); return false;
foreach ($this->patterns as $pattern) { }
if (preg_match($pattern, $path)) {
return false; if ($this->excludedPattern) {
} $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
$path = str_replace('\\', '/', $path);
return !preg_match($this->excludedPattern, $path);
} }
return true; return true;
@ -58,13 +71,14 @@ class ExcludeDirectoryFilterIterator extends FilterIterator implements \Recursiv
public function hasChildren() public function hasChildren()
{ {
return $this->isRecursive && $this->getInnerIterator()->hasChildren(); return $this->isRecursive && $this->iterator->hasChildren();
} }
public function getChildren() public function getChildren()
{ {
$children = new self($this->getInnerIterator()->getChildren(), array()); $children = new self($this->iterator->getChildren(), array());
$children->patterns = $this->patterns; $children->excludedDirs = $this->excludedDirs;
$children->excludedPattern = $this->excludedPattern;
return $children; return $children;
} }

View File

@ -31,6 +31,11 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
*/ */
private $rewindable; private $rewindable;
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
private $rootPath;
private $subPath;
private $directorySeparator = '/';
/** /**
* Constructor. * Constructor.
* *
@ -48,6 +53,10 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
parent::__construct($path, $flags); parent::__construct($path, $flags);
$this->ignoreUnreadableDirs = $ignoreUnreadableDirs; $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
$this->rootPath = (string) $path;
if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
$this->directorySeparator = DIRECTORY_SEPARATOR;
}
} }
/** /**
@ -57,7 +66,17 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
*/ */
public function current() public function current()
{ {
return new SplFileInfo(parent::current()->getPathname(), $this->getSubPath(), $this->getSubPathname()); // the logic here avoids redoing the same work in all iterations
if (null === $subPathname = $this->subPath) {
$subPathname = $this->subPath = (string) $this->getSubPath();
}
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
}
$subPathname .= $this->getFilename();
return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname);
} }
/** /**
@ -73,6 +92,10 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
if ($children instanceof self) { if ($children instanceof self) {
// parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
$children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
// performance optimization to avoid redoing the same work in all children
$children->rewindable = &$this->rewindable;
$children->rootPath = $this->rootPath;
} }
return $children; return $children;