Finder.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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;
  11. /**
  12. * Finder allows to build rules to find files and directories.
  13. *
  14. * It is a thin wrapper around several specialized iterator classes.
  15. *
  16. * All rules may be invoked several times.
  17. *
  18. * All methods return the current Finder object to allow easy chaining:
  19. *
  20. * $finder = new Finder();
  21. * $finder = $finder->files()->name('*.php')->in(__DIR__);
  22. *
  23. * @author Fabien Potencier <fabien@symfony.com>
  24. *
  25. * @api
  26. */
  27. class Finder implements \IteratorAggregate
  28. {
  29. private $mode = 0;
  30. private $names = array();
  31. private $notNames = array();
  32. private $exclude = array();
  33. private $filters = array();
  34. private $depths = array();
  35. private $sizes = array();
  36. private $followLinks = false;
  37. private $sort = false;
  38. private $ignoreVCS = true;
  39. private $dirs = array();
  40. private $dates = array();
  41. private $iterators = array();
  42. /**
  43. * Restricts the matching to directories only.
  44. *
  45. * @return Finder The current Finder instance
  46. *
  47. * @api
  48. */
  49. public function directories()
  50. {
  51. $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
  52. return $this;
  53. }
  54. /**
  55. * Restricts the matching to files only.
  56. *
  57. * @return Finder The current Finder instance
  58. *
  59. * @api
  60. */
  61. public function files()
  62. {
  63. $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
  64. return $this;
  65. }
  66. /**
  67. * Adds tests for the directory depth.
  68. *
  69. * Usage:
  70. *
  71. * $finder->depth('> 1') // the Finder will start matching at level 1.
  72. * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
  73. *
  74. * @param int $level The depth level expression
  75. *
  76. * @return Finder The current Finder instance
  77. *
  78. * @see Symfony\Component\Finder\Iterator\DepthRangeFilterIterator
  79. * @see Symfony\Component\Finder\Comparator\NumberComparator
  80. *
  81. * @api
  82. */
  83. public function depth($level)
  84. {
  85. $this->depths[] = new Comparator\NumberComparator($level);
  86. return $this;
  87. }
  88. /**
  89. * Adds tests for file dates (last modified).
  90. *
  91. * The date must be something that strtotime() is able to parse:
  92. *
  93. * $finder->date('since yesterday');
  94. * $finder->date('until 2 days ago');
  95. * $finder->date('> now - 2 hours');
  96. * $finder->date('>= 2005-10-15');
  97. *
  98. * @param string $date A date rage string
  99. *
  100. * @return Finder The current Finder instance
  101. *
  102. * @see strtotime
  103. * @see Symfony\Component\Finder\Iterator\DateRangeFilterIterator
  104. * @see Symfony\Component\Finder\Comparator\DateComparator
  105. *
  106. * @api
  107. */
  108. public function date($date)
  109. {
  110. $this->dates[] = new Comparator\DateComparator($date);
  111. return $this;
  112. }
  113. /**
  114. * Adds rules that files must match.
  115. *
  116. * You can use patterns (delimited with / sign), globs or simple strings.
  117. *
  118. * $finder->name('*.php')
  119. * $finder->name('/\.php$/') // same as above
  120. * $finder->name('test.php')
  121. *
  122. * @param string $pattern A pattern (a regexp, a glob, or a string)
  123. *
  124. * @return Finder The current Finder instance
  125. *
  126. * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
  127. *
  128. * @api
  129. */
  130. public function name($pattern)
  131. {
  132. $this->names[] = $pattern;
  133. return $this;
  134. }
  135. /**
  136. * Adds rules that files must not match.
  137. *
  138. * @param string $pattern A pattern (a regexp, a glob, or a string)
  139. *
  140. * @return Finder The current Finder instance
  141. *
  142. * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
  143. *
  144. * @api
  145. */
  146. public function notName($pattern)
  147. {
  148. $this->notNames[] = $pattern;
  149. return $this;
  150. }
  151. /**
  152. * Adds tests for file sizes.
  153. *
  154. * $finder->size('> 10K');
  155. * $finder->size('<= 1Ki');
  156. * $finder->size(4);
  157. *
  158. * @param string $size A size range string
  159. *
  160. * @return Finder The current Finder instance
  161. *
  162. * @see Symfony\Component\Finder\Iterator\SizeRangeFilterIterator
  163. * @see Symfony\Component\Finder\Comparator\NumberComparator
  164. *
  165. * @api
  166. */
  167. public function size($size)
  168. {
  169. $this->sizes[] = new Comparator\NumberComparator($size);
  170. return $this;
  171. }
  172. /**
  173. * Excludes directories.
  174. *
  175. * @param string $dir A directory to exclude
  176. *
  177. * @return Finder The current Finder instance
  178. *
  179. * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
  180. *
  181. * @api
  182. */
  183. public function exclude($dir)
  184. {
  185. $this->exclude[] = $dir;
  186. return $this;
  187. }
  188. /**
  189. * Forces the finder to ignore version control directories.
  190. *
  191. * @return Finder The current Finder instance
  192. *
  193. * @see Symfony\Component\Finder\Iterator\IgnoreVcsFilterIterator
  194. *
  195. * @api
  196. */
  197. public function ignoreVCS($ignoreVCS)
  198. {
  199. $this->ignoreVCS = (Boolean) $ignoreVCS;
  200. return $this;
  201. }
  202. /**
  203. * Sorts files and directories by an anonymous function.
  204. *
  205. * The anonymous function receives two \SplFileInfo instances to compare.
  206. *
  207. * This can be slow as all the matching files and directories must be retrieved for comparison.
  208. *
  209. * @param Closure $closure An anonymous function
  210. *
  211. * @return Finder The current Finder instance
  212. *
  213. * @see Symfony\Component\Finder\Iterator\SortableIterator
  214. *
  215. * @api
  216. */
  217. public function sort(\Closure $closure)
  218. {
  219. $this->sort = $closure;
  220. return $this;
  221. }
  222. /**
  223. * Sorts files and directories by name.
  224. *
  225. * This can be slow as all the matching files and directories must be retrieved for comparison.
  226. *
  227. * @return Finder The current Finder instance
  228. *
  229. * @see Symfony\Component\Finder\Iterator\SortableIterator
  230. *
  231. * @api
  232. */
  233. public function sortByName()
  234. {
  235. $this->sort = Iterator\SortableIterator::SORT_BY_NAME;
  236. return $this;
  237. }
  238. /**
  239. * Sorts files and directories by type (directories before files), then by name.
  240. *
  241. * This can be slow as all the matching files and directories must be retrieved for comparison.
  242. *
  243. * @return Finder The current Finder instance
  244. *
  245. * @see Symfony\Component\Finder\Iterator\SortableIterator
  246. *
  247. * @api
  248. */
  249. public function sortByType()
  250. {
  251. $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
  252. return $this;
  253. }
  254. /**
  255. * Filters the iterator with an anonymous function.
  256. *
  257. * The anonymous function receives a \SplFileInfo and must return false
  258. * to remove files.
  259. *
  260. * @param Closure $closure An anonymous function
  261. *
  262. * @return Finder The current Finder instance
  263. *
  264. * @see Symfony\Component\Finder\Iterator\CustomFilterIterator
  265. *
  266. * @api
  267. */
  268. public function filter(\Closure $closure)
  269. {
  270. $this->filters[] = $closure;
  271. return $this;
  272. }
  273. /**
  274. * Forces the following of symlinks.
  275. *
  276. * @return Finder The current Finder instance
  277. *
  278. * @api
  279. */
  280. public function followLinks()
  281. {
  282. $this->followLinks = true;
  283. return $this;
  284. }
  285. /**
  286. * Searches files and directories which match defined rules.
  287. *
  288. * @param string|array $dirs A directory path or an array of directories
  289. *
  290. * @return Finder The current Finder instance
  291. *
  292. * @throws \InvalidArgumentException if one of the directory does not exist
  293. *
  294. * @api
  295. */
  296. public function in($dirs)
  297. {
  298. $dirs = (array) $dirs;
  299. foreach ($dirs as $dir) {
  300. if (!is_dir($dir)) {
  301. throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir));
  302. }
  303. }
  304. $this->dirs = array_merge($this->dirs, $dirs);
  305. return $this;
  306. }
  307. /**
  308. * Returns an Iterator for the current Finder configuration.
  309. *
  310. * This method implements the IteratorAggregate interface.
  311. *
  312. * @return \Iterator An iterator
  313. *
  314. * @throws \LogicException if the in() method has not been called
  315. */
  316. public function getIterator()
  317. {
  318. if (0 === count($this->dirs)) {
  319. throw new \LogicException('You must call the in() method before iterating over a Finder.');
  320. }
  321. if (1 === count($this->dirs) && 0 === count($this->iterators)) {
  322. return $this->searchInDirectory($this->dirs[0]);
  323. }
  324. $iterator = new \AppendIterator();
  325. foreach ($this->dirs as $dir) {
  326. $iterator->append($this->searchInDirectory($dir));
  327. }
  328. foreach ($this->iterators as $it) {
  329. $iterator->append($it);
  330. }
  331. return $iterator;
  332. }
  333. /**
  334. * Appends an existing set of files/directories to the finder.
  335. *
  336. * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
  337. *
  338. * @param mixed $iterator
  339. */
  340. public function append($iterator)
  341. {
  342. if ($iterator instanceof \IteratorAggregate) {
  343. $this->iterators[] = $iterator->getIterator();
  344. } elseif ($iterator instanceof \Iterator) {
  345. $this->iterators[] = $iterator;
  346. } elseif ($iterator instanceof \Traversable || is_array($iterator)) {
  347. $it = new \ArrayIterator();
  348. foreach ($iterator as $file) {
  349. $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
  350. }
  351. $this->iterators[] = $it;
  352. } else {
  353. throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
  354. }
  355. }
  356. private function searchInDirectory($dir)
  357. {
  358. $flags = \RecursiveDirectoryIterator::SKIP_DOTS;
  359. if ($this->followLinks) {
  360. $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
  361. }
  362. $iterator = new \RecursiveIteratorIterator(
  363. new Iterator\RecursiveDirectoryIterator($dir, $flags),
  364. \RecursiveIteratorIterator::SELF_FIRST
  365. );
  366. if ($this->depths) {
  367. $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->depths);
  368. }
  369. if ($this->mode) {
  370. $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
  371. }
  372. if ($this->exclude) {
  373. $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
  374. }
  375. if ($this->ignoreVCS) {
  376. $iterator = new Iterator\IgnoreVcsFilterIterator($iterator);
  377. }
  378. if ($this->names || $this->notNames) {
  379. $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
  380. }
  381. if ($this->sizes) {
  382. $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
  383. }
  384. if ($this->dates) {
  385. $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
  386. }
  387. if ($this->filters) {
  388. $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
  389. }
  390. if ($this->sort) {
  391. $iterator = new Iterator\SortableIterator($iterator, $this->sort);
  392. }
  393. return $iterator;
  394. }
  395. }