RecursiveDirectoryIterator.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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. private bool $ignoreUnreadableDirs;
  21. private ?bool $rewindable = null;
  22. // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
  23. private string $rootPath;
  24. private string $subPath;
  25. private string $directorySeparator = '/';
  26. /**
  27. * @throws \RuntimeException
  28. */
  29. public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
  30. {
  31. if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
  32. throw new \RuntimeException('This iterator only support returning current as fileinfo.');
  33. }
  34. parent::__construct($path, $flags);
  35. $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
  36. $this->rootPath = $path;
  37. if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
  38. $this->directorySeparator = \DIRECTORY_SEPARATOR;
  39. }
  40. }
  41. /**
  42. * Return an instance of SplFileInfo with support for relative paths.
  43. */
  44. public function current(): SplFileInfo
  45. {
  46. // the logic here avoids redoing the same work in all iterations
  47. if (!isset($this->subPath)) {
  48. $this->subPath = $this->getSubPath();
  49. }
  50. $subPathname = $this->subPath;
  51. if ('' !== $subPathname) {
  52. $subPathname .= $this->directorySeparator;
  53. }
  54. $subPathname .= $this->getFilename();
  55. if ('/' !== $basePath = $this->rootPath) {
  56. $basePath .= $this->directorySeparator;
  57. }
  58. return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
  59. }
  60. public function hasChildren(bool $allowLinks = false): bool
  61. {
  62. $hasChildren = parent::hasChildren($allowLinks);
  63. if (!$hasChildren || !$this->ignoreUnreadableDirs) {
  64. return $hasChildren;
  65. }
  66. try {
  67. parent::getChildren();
  68. return true;
  69. } catch (\UnexpectedValueException $e) {
  70. // If directory is unreadable and finder is set to ignore it, skip children
  71. return false;
  72. }
  73. }
  74. /**
  75. * @throws AccessDeniedException
  76. */
  77. public function getChildren(): \RecursiveDirectoryIterator
  78. {
  79. try {
  80. $children = parent::getChildren();
  81. if ($children instanceof self) {
  82. // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
  83. $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
  84. // performance optimization to avoid redoing the same work in all children
  85. $children->rewindable = &$this->rewindable;
  86. $children->rootPath = $this->rootPath;
  87. }
  88. return $children;
  89. } catch (\UnexpectedValueException $e) {
  90. throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
  91. }
  92. }
  93. /**
  94. * Do nothing for non rewindable stream.
  95. */
  96. public function rewind(): void
  97. {
  98. if (false === $this->isRewindable()) {
  99. return;
  100. }
  101. parent::rewind();
  102. }
  103. /**
  104. * Checks if the stream is rewindable.
  105. */
  106. public function isRewindable(): bool
  107. {
  108. if (null !== $this->rewindable) {
  109. return $this->rewindable;
  110. }
  111. if (false !== $stream = @opendir($this->getPath())) {
  112. $infos = stream_get_meta_data($stream);
  113. closedir($stream);
  114. if ($infos['seekable']) {
  115. return $this->rewindable = true;
  116. }
  117. }
  118. return $this->rewindable = false;
  119. }
  120. }