RecursiveDirectoryIterator.php 4.0 KB

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