Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Finder\Iterator; Chris@0: Chris@0: use Symfony\Component\Finder\Exception\AccessDeniedException; Chris@0: use Symfony\Component\Finder\SplFileInfo; Chris@0: Chris@0: /** Chris@0: * Extends the \RecursiveDirectoryIterator to support relative paths. Chris@0: * Chris@0: * @author Victor Berchet Chris@0: */ Chris@0: class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator Chris@0: { Chris@0: /** Chris@0: * @var bool Chris@0: */ Chris@0: private $ignoreUnreadableDirs; Chris@0: Chris@0: /** Chris@0: * @var bool Chris@0: */ Chris@0: private $rewindable; Chris@0: Chris@0: // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations Chris@0: private $rootPath; Chris@0: private $subPath; Chris@0: private $directorySeparator = '/'; Chris@0: Chris@0: /** Chris@0: * @param string $path Chris@0: * @param int $flags Chris@0: * @param bool $ignoreUnreadableDirs Chris@0: * Chris@0: * @throws \RuntimeException Chris@0: */ Chris@0: public function __construct($path, $flags, $ignoreUnreadableDirs = false) Chris@0: { Chris@0: if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { Chris@0: throw new \RuntimeException('This iterator only support returning current as fileinfo.'); Chris@0: } Chris@0: Chris@0: parent::__construct($path, $flags); Chris@0: $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; Chris@0: $this->rootPath = $path; Chris@17: if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { Chris@17: $this->directorySeparator = \DIRECTORY_SEPARATOR; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Return an instance of SplFileInfo with support for relative paths. Chris@0: * Chris@0: * @return SplFileInfo File information Chris@0: */ Chris@0: public function current() Chris@0: { Chris@0: // the logic here avoids redoing the same work in all iterations Chris@0: Chris@0: if (null === $subPathname = $this->subPath) { Chris@0: $subPathname = $this->subPath = (string) $this->getSubPath(); Chris@0: } Chris@0: if ('' !== $subPathname) { Chris@0: $subPathname .= $this->directorySeparator; Chris@0: } Chris@0: $subPathname .= $this->getFilename(); Chris@0: Chris@0: return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return \RecursiveIterator Chris@0: * Chris@0: * @throws AccessDeniedException Chris@0: */ Chris@0: public function getChildren() Chris@0: { Chris@0: try { Chris@0: $children = parent::getChildren(); Chris@0: Chris@0: if ($children instanceof self) { Chris@0: // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore Chris@0: $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; Chris@0: Chris@0: // performance optimization to avoid redoing the same work in all children Chris@0: $children->rewindable = &$this->rewindable; Chris@0: $children->rootPath = $this->rootPath; Chris@0: } Chris@0: Chris@0: return $children; Chris@0: } catch (\UnexpectedValueException $e) { Chris@0: if ($this->ignoreUnreadableDirs) { Chris@0: // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. Chris@17: return new \RecursiveArrayIterator([]); Chris@0: } else { Chris@0: throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Do nothing for non rewindable stream. Chris@0: */ Chris@0: public function rewind() Chris@0: { Chris@0: if (false === $this->isRewindable()) { Chris@0: return; Chris@0: } Chris@0: Chris@0: // @see https://bugs.php.net/68557 Chris@0: if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) { Chris@0: parent::next(); Chris@0: } Chris@0: Chris@0: parent::rewind(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if the stream is rewindable. Chris@0: * Chris@0: * @return bool true when the stream is rewindable, false otherwise Chris@0: */ Chris@0: public function isRewindable() Chris@0: { Chris@0: if (null !== $this->rewindable) { Chris@0: return $this->rewindable; Chris@0: } Chris@0: Chris@0: // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed Chris@0: if ('' === $this->getPath()) { Chris@0: return $this->rewindable = false; Chris@0: } Chris@0: Chris@0: if (false !== $stream = @opendir($this->getPath())) { Chris@0: $infos = stream_get_meta_data($stream); Chris@0: closedir($stream); Chris@0: Chris@0: if ($infos['seekable']) { Chris@0: return $this->rewindable = true; Chris@0: } Chris@0: } Chris@0: Chris@0: return $this->rewindable = false; Chris@0: } Chris@0: }