diff --git a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php index 477a58a073..46473b3d5a 100644 --- a/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/AbstractAdapter.php @@ -33,6 +33,7 @@ abstract class AbstractAdapter implements AdapterInterface protected $sort = false; protected $paths = array(); protected $notPaths = array(); + protected $ignoreUnreadableDirs = false; private static $areSupported = array(); @@ -210,6 +211,16 @@ abstract class AbstractAdapter implements AdapterInterface return $this; } + /** + * {@inheritdoc} + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (Boolean) $ignore; + + return $this; + } + /** * Returns whether the adapter is supported in the current environment. * diff --git a/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php b/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php index d63478339a..60bebba15c 100644 --- a/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/AbstractFindAdapter.php @@ -92,9 +92,12 @@ abstract class AbstractFindAdapter extends AbstractAdapter $this->buildSorting($command, $this->sort); } - $command->setErrorHandler(function ($stderr) { - throw new AccessDeniedException($stderr); - }); + $command->setErrorHandler( + $this->ignoreUnreadableDirs + // If directory is unreadable and finder is set to ignore it, `stderr` is ignored. + ? function ($stderr) { return; } + : function ($stderr) { throw new AccessDeniedException($stderr); } + ); $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute()); $iterator = new Iterator\FilePathsIterator($paths, $dir); diff --git a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php index f28ffb3189..50c018bc50 100644 --- a/src/Symfony/Component/Finder/Adapter/AdapterInterface.php +++ b/src/Symfony/Component/Finder/Adapter/AdapterInterface.php @@ -114,6 +114,13 @@ interface AdapterInterface */ public function setNotPath(array $notPaths); + /** + * @param boolean $ignore + * + * @return AdapterInterface Current instance + */ + public function ignoreUnreadableDirs($ignore = true); + /** * @param string $dir * diff --git a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php index 7abdcdad52..378a26acda 100644 --- a/src/Symfony/Component/Finder/Adapter/PhpAdapter.php +++ b/src/Symfony/Component/Finder/Adapter/PhpAdapter.php @@ -32,7 +32,7 @@ class PhpAdapter extends AbstractAdapter } $iterator = new \RecursiveIteratorIterator( - new Iterator\RecursiveDirectoryIterator($dir, $flags), + new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs), \RecursiveIteratorIterator::SELF_FIRST ); diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index f3ba20cf04..43a8643650 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -55,6 +55,7 @@ class Finder implements \IteratorAggregate, \Countable private $adapters = array(); private $paths = array(); private $notPaths = array(); + private $ignoreUnreadableDirs = false; private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); @@ -626,6 +627,22 @@ class Finder implements \IteratorAggregate, \Countable return $this; } + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param boolean $ignore + * + * @return Finder The current Finder instance + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (Boolean) $ignore; + + return $this; + } + /** * Searches files and directories which match defined rules. * @@ -794,7 +811,8 @@ class Finder implements \IteratorAggregate, \Countable ->setFilters($this->filters) ->setSort($this->sort) ->setPath($this->paths) - ->setNotPath($this->notPaths); + ->setNotPath($this->notPaths) + ->ignoreUnreadableDirs($this->ignoreUnreadableDirs); } /** diff --git a/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php b/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php index 0fafcdb61e..d08f644088 100644 --- a/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php +++ b/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php @@ -21,13 +21,28 @@ use Symfony\Component\Finder\SplFileInfo; */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { - public function __construct($path, $flags) + /** + * @var boolean + */ + private $ignoreUnreadableDirs; + + /** + * Constructor. + * + * @param string $path + * @param int $flags + * @param boolean $ignoreUnreadableDirs + * + * @throws \RuntimeException + */ + public function __construct($path, $flags, $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; } /** @@ -41,7 +56,7 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator } /** - * @return mixed object + * @return \RecursiveIterator * * @throws AccessDeniedException */ @@ -50,7 +65,12 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator try { return parent::getChildren(); } catch (\UnexpectedValueException $e) { - throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator(array()); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } } } } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index 89b04ceca3..12d029e593 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -725,16 +725,34 @@ class FinderTest extends Iterator\RealIteratorTestCase $finder = $this->buildFinder($adapter); $finder->files()->in(self::$tmpDir); - // make 'foo' directory non-openable + // make 'foo' directory non-readable chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333); try { - $this->assertIterator($this->toAbsolute(array('test.php', 'test.py')), $finder->getIterator()); + $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator()); $this->fail('Finder should throw an exception when opening a non-readable directory.'); } catch (\Exception $e) { $this->assertEquals('Symfony\\Component\\Finder\\Exception\\AccessDeniedException', get_class($e)); } + // restore original permissions + chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777); + } + + /** + * @dataProvider getAdaptersTestData + */ + public function testIgnoredAccessDeniedException(Adapter\AdapterInterface $adapter) + { + $finder = $this->buildFinder($adapter); + $finder->files()->ignoreUnreadableDirs()->in(self::$tmpDir); + + // make 'foo' directory non-readable + chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0333); + + $this->assertIterator($this->toAbsolute(array('foo bar', 'test.php', 'test.py')), $finder->getIterator()); + + // restore original permissions chmod(self::$tmpDir.DIRECTORY_SEPARATOR.'foo', 0777); }