RecursiveDirectoryIterator.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Finder\Iterator;
  11. use Symfony\Component\Finder\Exception\AccessDeniedException;
  12. use Symfony\Component\Finder\SplFileInfo;
  13. /**
  14. * Extends the \RecursiveDirectoryIterator to support relative paths.
  15. *
  16. * @author Victor Berchet <victor@suumit.com>
  17. */
  18. class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
  19. {
  20. /**
  21. * @var bool
  22. */
  23. private $ignoreUnreadableDirs;
  24. /**
  25. * @var bool
  26. */
  27. private $rewindable;
  28. // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
  29. private $rootPath;
  30. private $subPath;
  31. private $directorySeparator = '/';
  32. /**
  33. * Constructor.
  34. *
  35. * @param string $path
  36. * @param int $flags
  37. * @param bool $ignoreUnreadableDirs
  38. *
  39. * @throws \RuntimeException
  40. */
  41. public function __construct($path, $flags, $ignoreUnreadableDirs = false)
  42. {
  43. if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
  44. throw new \RuntimeException('This iterator only support returning current as fileinfo.');
  45. }
  46. parent::__construct($path, $flags);
  47. $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
  48. $this->rootPath = $path;
  49. if ('/' !== DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
  50. $this->directorySeparator = DIRECTORY_SEPARATOR;
  51. }
  52. }
  53. /**
  54. * Return an instance of SplFileInfo with support for relative paths.
  55. *
  56. * @return SplFileInfo File information
  57. */
  58. public function current()
  59. {
  60. // the logic here avoids redoing the same work in all iterations
  61. if (null === $subPathname = $this->subPath) {
  62. $subPathname = $this->subPath = (string) $this->getSubPath();
  63. }
  64. if ('' !== $subPathname) {
  65. $subPathname .= $this->directorySeparator;
  66. }
  67. $subPathname .= $this->getFilename();
  68. return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname);
  69. }
  70. /**
  71. * @return \RecursiveIterator
  72. *
  73. * @throws AccessDeniedException
  74. */
  75. public function getChildren()
  76. {
  77. try {
  78. $children = parent::getChildren();
  79. if ($children instanceof self) {
  80. // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
  81. $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
  82. // performance optimization to avoid redoing the same work in all children
  83. $children->rewindable = &$this->rewindable;
  84. $children->rootPath = $this->rootPath;
  85. }
  86. return $children;
  87. } catch (\UnexpectedValueException $e) {
  88. if ($this->ignoreUnreadableDirs) {
  89. // If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
  90. return new \RecursiveArrayIterator(array());
  91. } else {
  92. throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
  93. }
  94. }
  95. }
  96. /**
  97. * Do nothing for non rewindable stream.
  98. */
  99. public function rewind()
  100. {
  101. if (false === $this->isRewindable()) {
  102. return;
  103. }
  104. // @see https://bugs.php.net/68557
  105. if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) {
  106. parent::next();
  107. }
  108. parent::rewind();
  109. }
  110. /**
  111. * Checks if the stream is rewindable.
  112. *
  113. * @return bool true when the stream is rewindable, false otherwise
  114. */
  115. public function isRewindable()
  116. {
  117. if (null !== $this->rewindable) {
  118. return $this->rewindable;
  119. }
  120. // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed
  121. if ('' === $this->getPath()) {
  122. return $this->rewindable = false;
  123. }
  124. if (false !== $stream = @opendir($this->getPath())) {
  125. $infos = stream_get_meta_data($stream);
  126. closedir($stream);
  127. if ($infos['seekable']) {
  128. return $this->rewindable = true;
  129. }
  130. }
  131. return $this->rewindable = false;
  132. }
  133. }