RecursiveDirectoryIterator.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  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 $ignoreFirstRewind = true;
  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. * @throws \RuntimeException
  34. */
  35. public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
  36. {
  37. if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
  38. throw new \RuntimeException('This iterator only support returning current as fileinfo.');
  39. }
  40. parent::__construct($path, $flags);
  41. $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
  42. $this->rootPath = $path;
  43. if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
  44. $this->directorySeparator = \DIRECTORY_SEPARATOR;
  45. }
  46. }
  47. /**
  48. * Return an instance of SplFileInfo with support for relative paths.
  49. *
  50. * @return SplFileInfo
  51. */
  52. #[\ReturnTypeWillChange]
  53. public function current()
  54. {
  55. // the logic here avoids redoing the same work in all iterations
  56. if (null === $subPathname = $this->subPath) {
  57. $subPathname = $this->subPath = $this->getSubPath();
  58. }
  59. if ('' !== $subPathname) {
  60. $subPathname .= $this->directorySeparator;
  61. }
  62. $subPathname .= $this->getFilename();
  63. $basePath = $this->rootPath;
  64. if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) {
  65. $basePath .= $this->directorySeparator;
  66. }
  67. return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
  68. }
  69. /**
  70. * @param bool $allowLinks
  71. *
  72. * @return bool
  73. */
  74. #[\ReturnTypeWillChange]
  75. public function hasChildren($allowLinks = false)
  76. {
  77. $hasChildren = parent::hasChildren($allowLinks);
  78. if (!$hasChildren || !$this->ignoreUnreadableDirs) {
  79. return $hasChildren;
  80. }
  81. try {
  82. parent::getChildren();
  83. return true;
  84. } catch (\UnexpectedValueException $e) {
  85. // If directory is unreadable and finder is set to ignore it, skip children
  86. return false;
  87. }
  88. }
  89. /**
  90. * @return \RecursiveDirectoryIterator
  91. *
  92. * @throws AccessDeniedException
  93. */
  94. #[\ReturnTypeWillChange]
  95. public function getChildren()
  96. {
  97. try {
  98. $children = parent::getChildren();
  99. if ($children instanceof self) {
  100. // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
  101. $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
  102. // performance optimization to avoid redoing the same work in all children
  103. $children->rootPath = $this->rootPath;
  104. }
  105. return $children;
  106. } catch (\UnexpectedValueException $e) {
  107. throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
  108. }
  109. }
  110. /**
  111. * @return void
  112. */
  113. #[\ReturnTypeWillChange]
  114. public function next()
  115. {
  116. $this->ignoreFirstRewind = false;
  117. parent::next();
  118. }
  119. /**
  120. * @return void
  121. */
  122. #[\ReturnTypeWillChange]
  123. public function rewind()
  124. {
  125. // some streams like FTP are not rewindable, ignore the first rewind after creation,
  126. // as newly created DirectoryIterator does not need to be rewound
  127. if ($this->ignoreFirstRewind) {
  128. $this->ignoreFirstRewind = false;
  129. return;
  130. }
  131. parent::rewind();
  132. }
  133. }