From 169c0b93b5d90dc5d42c4316dd8016835d87934a Mon Sep 17 00:00:00 2001 From: alquerci Date: Fri, 24 May 2013 12:46:33 +0200 Subject: [PATCH] [Finder] Fix iteration fails with non-rewindable streams --- .../Finder/Iterator/FilterIterator.php | 9 +- .../Iterator/RecursiveDirectoryIterator.php | 40 +++++++++ .../Component/Finder/Tests/FinderTest.php | 17 ++++ .../Tests/Iterator/IteratorTestCase.php | 37 +++++++++ .../RecursiveDirectoryIteratorTest.php | 83 +++++++++++++++++++ 5 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php diff --git a/src/Symfony/Component/Finder/Iterator/FilterIterator.php b/src/Symfony/Component/Finder/Iterator/FilterIterator.php index 64da508ebd..f4da44c4cd 100644 --- a/src/Symfony/Component/Finder/Iterator/FilterIterator.php +++ b/src/Symfony/Component/Finder/Iterator/FilterIterator.php @@ -30,7 +30,14 @@ abstract class FilterIterator extends \FilterIterator { $iterator = $this; while ($iterator instanceof \OuterIterator) { - if ($iterator->getInnerIterator() instanceof \FilesystemIterator) { + $innerIterator = $iterator->getInnerIterator(); + + if ($innerIterator instanceof RecursiveDirectoryIterator) { + if ($innerIterator->isRewindable()) { + $innerIterator->next(); + $innerIterator->rewind(); + } + } elseif ($iterator->getInnerIterator() instanceof \FilesystemIterator) { $iterator->getInnerIterator()->next(); $iterator->getInnerIterator()->rewind(); } diff --git a/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php b/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php index 508d11bf3c..94f61b7a34 100644 --- a/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php +++ b/src/Symfony/Component/Finder/Iterator/RecursiveDirectoryIterator.php @@ -20,6 +20,11 @@ use Symfony\Component\Finder\SplFileInfo; */ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { + /** + * @var Boolean + */ + private $rewindable; + public function __construct($path, $flags) { if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { @@ -38,4 +43,39 @@ class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator { return new SplFileInfo(parent::current()->getPathname(), $this->getSubPath(), $this->getSubPathname()); } + + /** + * Do nothing for non rewindable stream + */ + public function rewind() + { + if (false === $this->isRewindable()) { + return; + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return Boolean true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } } diff --git a/src/Symfony/Component/Finder/Tests/FinderTest.php b/src/Symfony/Component/Finder/Tests/FinderTest.php index ec7db1ec06..40d18be0e8 100644 --- a/src/Symfony/Component/Finder/Tests/FinderTest.php +++ b/src/Symfony/Component/Finder/Tests/FinderTest.php @@ -459,4 +459,21 @@ class FinderTest extends Iterator\RealIteratorTestCase $this->assertEquals(1, count($finder)); } + + public function testNonSeekableStream() + { + try { + $i = Finder::create()->in('ftp://ftp.mozilla.org/')->depth(0)->getIterator(); + } catch (\UnexpectedValueException $e) { + $this->markTestSkipped(sprintf('Unsupported stream "%s".', 'ftp')); + } + + $contains = array( + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub', + ); + + $this->assertIteratorInForeach($contains, $i); + } } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php index 8810ce757d..845fc2c513 100644 --- a/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php +++ b/src/Symfony/Component/Finder/Tests/Iterator/IteratorTestCase.php @@ -29,4 +29,41 @@ abstract class IteratorTestCase extends \PHPUnit_Framework_TestCase $this->assertEquals($expected, array_values($values)); } + + /** + * Same as IteratorTestCase::assertIterator with foreach usage + * + * @param array $expected + * @param \Traversable $iterator + */ + protected function assertIteratorInForeach($expected, \Traversable $iterator) + { + $values = array(); + foreach ($iterator as $file) { + $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file); + $values[] = $file->getPathname(); + } + + sort($values); + sort($expected); + + $this->assertEquals($expected, array_values($values)); + } + + /** + * Same as IteratorTestCase::assertOrderedIterator with foreach usage + * + * @param array $expected + * @param \Traversable $iterator + */ + protected function assertOrderedIteratorInForeach($expected, \Traversable $iterator) + { + $values = array(); + foreach ($iterator as $file) { + $this->assertInstanceOf('Symfony\\Component\\Finder\\SplFileInfo', $file); + $values[] = $file->getPathname(); + } + + $this->assertEquals($expected, array_values($values)); + } } diff --git a/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php b/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php new file mode 100644 index 0000000000..f762514346 --- /dev/null +++ b/src/Symfony/Component/Finder/Tests/Iterator/RecursiveDirectoryIteratorTest.php @@ -0,0 +1,83 @@ + +* +* 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 Symfony\Component\Finder\Iterator\RecursiveDirectoryIterator; + +class RecursiveDirectoryIteratorTest extends IteratorTestCase +{ + /** + * @dataProvider getPaths + * + * @param string $path + * @param Boolean $seekable + * @param Boolean $supports + * @param string $message + */ + public function testRewind($path, $seekable, $contains, $message = null) + { + try { + $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + } catch (\UnexpectedValueException $e) { + $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path)); + } + + $i->rewind(); + + $this->assertTrue(true, $message); + } + + /** + * @dataProvider getPaths + * + * @param string $path + * @param Boolean $seekable + * @param Boolean $supports + * @param string $message + */ + public function testSeek($path, $seekable, $contains, $message = null) + { + try { + $i = new RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS); + } catch (\UnexpectedValueException $e) { + $this->markTestSkipped(sprintf('Unsupported stream "%s".', $path)); + } + + $actual = array(); + + $i->seek(0); + $actual[] = $i->getPathname(); + + $i->seek(1); + $actual[] = $i->getPathname(); + + $i->seek(2); + $actual[] = $i->getPathname(); + + $this->assertEquals($contains, $actual); + } + + public function getPaths() + { + $data = array(); + + // ftp + $contains = array( + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'README', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'index.html', + 'ftp://ftp.mozilla.org'.DIRECTORY_SEPARATOR.'pub', + ); + $data[] = array('ftp://ftp.mozilla.org/', false, $contains); + + return $data; + } +}