PageRenderTime 45ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/symfony/finder/Symfony/Component/Finder/Finder.php

https://bitbucket.org/vervcreations/projectis.at
PHP | 811 lines | 311 code | 87 blank | 413 comment | 19 complexity | 1a87c7a11d92e9a35c3f08b42a18709e MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1, BSD-3-Clause
  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. use Symfony\Component\Finder\Adapter\AdapterInterface;
  12. use Symfony\Component\Finder\Adapter\GnuFindAdapter;
  13. use Symfony\Component\Finder\Adapter\BsdFindAdapter;
  14. use Symfony\Component\Finder\Adapter\PhpAdapter;
  15. use Symfony\Component\Finder\Exception\ExceptionInterface;
  16. /**
  17. * Finder allows to build rules to find files and directories.
  18. *
  19. * It is a thin wrapper around several specialized iterator classes.
  20. *
  21. * All rules may be invoked several times.
  22. *
  23. * All methods return the current Finder object to allow easy chaining:
  24. *
  25. * $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
  26. *
  27. * @author Fabien Potencier <fabien@symfony.com>
  28. *
  29. * @api
  30. */
  31. class Finder implements \IteratorAggregate, \Countable
  32. {
  33. const IGNORE_VCS_FILES = 1;
  34. const IGNORE_DOT_FILES = 2;
  35. private $mode = 0;
  36. private $names = array();
  37. private $notNames = array();
  38. private $exclude = array();
  39. private $filters = array();
  40. private $depths = array();
  41. private $sizes = array();
  42. private $followLinks = false;
  43. private $sort = false;
  44. private $ignore = 0;
  45. private $dirs = array();
  46. private $dates = array();
  47. private $iterators = array();
  48. private $contains = array();
  49. private $notContains = array();
  50. private $adapters = array();
  51. private $paths = array();
  52. private $notPaths = array();
  53. private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg');
  54. /**
  55. * Constructor.
  56. */
  57. public function __construct()
  58. {
  59. $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
  60. $this
  61. ->addAdapter(new GnuFindAdapter())
  62. ->addAdapter(new BsdFindAdapter())
  63. ->addAdapter(new PhpAdapter(), -50)
  64. ->setAdapter('php')
  65. ;
  66. }
  67. /**
  68. * Creates a new Finder.
  69. *
  70. * @return Finder A new Finder instance
  71. *
  72. * @api
  73. */
  74. public static function create()
  75. {
  76. return new static();
  77. }
  78. /**
  79. * Registers a finder engine implementation.
  80. *
  81. * @param AdapterInterface $adapter An adapter instance
  82. * @param integer $priority Highest is selected first
  83. *
  84. * @return Finder The current Finder instance
  85. */
  86. public function addAdapter(Adapter\AdapterInterface $adapter, $priority = 0)
  87. {
  88. $this->adapters[$adapter->getName()] = array(
  89. 'adapter' => $adapter,
  90. 'priority' => $priority,
  91. 'selected' => false,
  92. );
  93. return $this->sortAdapters();
  94. }
  95. /**
  96. * Sets the selected adapter to the best one according to the current platform the code is run on.
  97. *
  98. * @return Finder The current Finder instance
  99. */
  100. public function useBestAdapter()
  101. {
  102. $this->resetAdapterSelection();
  103. return $this->sortAdapters();
  104. }
  105. /**
  106. * Selects the adapter to use.
  107. *
  108. * @param string $name
  109. *
  110. * @throws \InvalidArgumentException
  111. *
  112. * @return Finder The current Finder instance
  113. */
  114. public function setAdapter($name)
  115. {
  116. if (!isset($this->adapters[$name])) {
  117. throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name));
  118. }
  119. $this->resetAdapterSelection();
  120. $this->adapters[$name]['selected'] = true;
  121. return $this->sortAdapters();
  122. }
  123. /**
  124. * Removes all adapters registered in the finder.
  125. *
  126. * @return Finder The current Finder instance
  127. */
  128. public function removeAdapters()
  129. {
  130. $this->adapters = array();
  131. return $this;
  132. }
  133. /**
  134. * Returns registered adapters ordered by priority without extra information.
  135. *
  136. * @return AdapterInterface[]
  137. */
  138. public function getAdapters()
  139. {
  140. return array_values(array_map(function(array $adapter) {
  141. return $adapter['adapter'];
  142. }, $this->adapters));
  143. }
  144. /**
  145. * Restricts the matching to directories only.
  146. *
  147. * @return Finder The current Finder instance
  148. *
  149. * @api
  150. */
  151. public function directories()
  152. {
  153. $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
  154. return $this;
  155. }
  156. /**
  157. * Restricts the matching to files only.
  158. *
  159. * @return Finder The current Finder instance
  160. *
  161. * @api
  162. */
  163. public function files()
  164. {
  165. $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
  166. return $this;
  167. }
  168. /**
  169. * Adds tests for the directory depth.
  170. *
  171. * Usage:
  172. *
  173. * $finder->depth('> 1') // the Finder will start matching at level 1.
  174. * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
  175. *
  176. * @param int $level The depth level expression
  177. *
  178. * @return Finder The current Finder instance
  179. *
  180. * @see Symfony\Component\Finder\Iterator\DepthRangeFilterIterator
  181. * @see Symfony\Component\Finder\Comparator\NumberComparator
  182. *
  183. * @api
  184. */
  185. public function depth($level)
  186. {
  187. $this->depths[] = new Comparator\NumberComparator($level);
  188. return $this;
  189. }
  190. /**
  191. * Adds tests for file dates (last modified).
  192. *
  193. * The date must be something that strtotime() is able to parse:
  194. *
  195. * $finder->date('since yesterday');
  196. * $finder->date('until 2 days ago');
  197. * $finder->date('> now - 2 hours');
  198. * $finder->date('>= 2005-10-15');
  199. *
  200. * @param string $date A date rage string
  201. *
  202. * @return Finder The current Finder instance
  203. *
  204. * @see strtotime
  205. * @see Symfony\Component\Finder\Iterator\DateRangeFilterIterator
  206. * @see Symfony\Component\Finder\Comparator\DateComparator
  207. *
  208. * @api
  209. */
  210. public function date($date)
  211. {
  212. $this->dates[] = new Comparator\DateComparator($date);
  213. return $this;
  214. }
  215. /**
  216. * Adds rules that files must match.
  217. *
  218. * You can use patterns (delimited with / sign), globs or simple strings.
  219. *
  220. * $finder->name('*.php')
  221. * $finder->name('/\.php$/') // same as above
  222. * $finder->name('test.php')
  223. *
  224. * @param string $pattern A pattern (a regexp, a glob, or a string)
  225. *
  226. * @return Finder The current Finder instance
  227. *
  228. * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
  229. *
  230. * @api
  231. */
  232. public function name($pattern)
  233. {
  234. $this->names[] = $pattern;
  235. return $this;
  236. }
  237. /**
  238. * Adds rules that files must not match.
  239. *
  240. * @param string $pattern A pattern (a regexp, a glob, or a string)
  241. *
  242. * @return Finder The current Finder instance
  243. *
  244. * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
  245. *
  246. * @api
  247. */
  248. public function notName($pattern)
  249. {
  250. $this->notNames[] = $pattern;
  251. return $this;
  252. }
  253. /**
  254. * Adds tests that file contents must match.
  255. *
  256. * Strings or PCRE patterns can be used:
  257. *
  258. * $finder->contains('Lorem ipsum')
  259. * $finder->contains('/Lorem ipsum/i')
  260. *
  261. * @param string $pattern A pattern (string or regexp)
  262. *
  263. * @return Finder The current Finder instance
  264. *
  265. * @see Symfony\Component\Finder\Iterator\FilecontentFilterIterator
  266. */
  267. public function contains($pattern)
  268. {
  269. $this->contains[] = $pattern;
  270. return $this;
  271. }
  272. /**
  273. * Adds tests that file contents must not match.
  274. *
  275. * Strings or PCRE patterns can be used:
  276. *
  277. * $finder->notContains('Lorem ipsum')
  278. * $finder->notContains('/Lorem ipsum/i')
  279. *
  280. * @param string $pattern A pattern (string or regexp)
  281. *
  282. * @return Finder The current Finder instance
  283. *
  284. * @see Symfony\Component\Finder\Iterator\FilecontentFilterIterator
  285. */
  286. public function notContains($pattern)
  287. {
  288. $this->notContains[] = $pattern;
  289. return $this;
  290. }
  291. /**
  292. * Adds rules that filenames must match.
  293. *
  294. * You can use patterns (delimited with / sign) or simple strings.
  295. *
  296. * $finder->path('some/special/dir')
  297. * $finder->path('/some\/special\/dir/') // same as above
  298. *
  299. * Use only / as dirname separator.
  300. *
  301. * @param string $pattern A pattern (a regexp or a string)
  302. *
  303. * @return Finder The current Finder instance
  304. *
  305. * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
  306. */
  307. public function path($pattern)
  308. {
  309. $this->paths[] = $pattern;
  310. return $this;
  311. }
  312. /**
  313. * Adds rules that filenames must not match.
  314. *
  315. * You can use patterns (delimited with / sign) or simple strings.
  316. *
  317. * $finder->notPath('some/special/dir')
  318. * $finder->notPath('/some\/special\/dir/') // same as above
  319. *
  320. * Use only / as dirname separator.
  321. *
  322. * @param string $pattern A pattern (a regexp or a string)
  323. *
  324. * @return Finder The current Finder instance
  325. *
  326. * @see Symfony\Component\Finder\Iterator\FilenameFilterIterator
  327. */
  328. public function notPath($pattern)
  329. {
  330. $this->notPaths[] = $pattern;
  331. return $this;
  332. }
  333. /**
  334. * Adds tests for file sizes.
  335. *
  336. * $finder->size('> 10K');
  337. * $finder->size('<= 1Ki');
  338. * $finder->size(4);
  339. *
  340. * @param string $size A size range string
  341. *
  342. * @return Finder The current Finder instance
  343. *
  344. * @see Symfony\Component\Finder\Iterator\SizeRangeFilterIterator
  345. * @see Symfony\Component\Finder\Comparator\NumberComparator
  346. *
  347. * @api
  348. */
  349. public function size($size)
  350. {
  351. $this->sizes[] = new Comparator\NumberComparator($size);
  352. return $this;
  353. }
  354. /**
  355. * Excludes directories.
  356. *
  357. * @param string|array $dirs A directory path or an array of directories
  358. *
  359. * @return Finder The current Finder instance
  360. *
  361. * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
  362. *
  363. * @api
  364. */
  365. public function exclude($dirs)
  366. {
  367. $this->exclude = array_merge($this->exclude, (array) $dirs);
  368. return $this;
  369. }
  370. /**
  371. * Excludes "hidden" directories and files (starting with a dot).
  372. *
  373. * @param Boolean $ignoreDotFiles Whether to exclude "hidden" files or not
  374. *
  375. * @return Finder The current Finder instance
  376. *
  377. * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
  378. *
  379. * @api
  380. */
  381. public function ignoreDotFiles($ignoreDotFiles)
  382. {
  383. if ($ignoreDotFiles) {
  384. $this->ignore = $this->ignore | static::IGNORE_DOT_FILES;
  385. } else {
  386. $this->ignore = $this->ignore & ~static::IGNORE_DOT_FILES;
  387. }
  388. return $this;
  389. }
  390. /**
  391. * Forces the finder to ignore version control directories.
  392. *
  393. * @param Boolean $ignoreVCS Whether to exclude VCS files or not
  394. *
  395. * @return Finder The current Finder instance
  396. *
  397. * @see Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator
  398. *
  399. * @api
  400. */
  401. public function ignoreVCS($ignoreVCS)
  402. {
  403. if ($ignoreVCS) {
  404. $this->ignore = $this->ignore | static::IGNORE_VCS_FILES;
  405. } else {
  406. $this->ignore = $this->ignore & ~static::IGNORE_VCS_FILES;
  407. }
  408. return $this;
  409. }
  410. /**
  411. * Adds VCS patterns.
  412. *
  413. * @see ignoreVCS
  414. *
  415. * @param string|string[] $pattern VCS patterns to ignore
  416. */
  417. public static function addVCSPattern($pattern)
  418. {
  419. foreach ((array) $pattern as $p) {
  420. self::$vcsPatterns[] = $p;
  421. }
  422. self::$vcsPatterns = array_unique(self::$vcsPatterns);
  423. }
  424. /**
  425. * Sorts files and directories by an anonymous function.
  426. *
  427. * The anonymous function receives two \SplFileInfo instances to compare.
  428. *
  429. * This can be slow as all the matching files and directories must be retrieved for comparison.
  430. *
  431. * @param \Closure $closure An anonymous function
  432. *
  433. * @return Finder The current Finder instance
  434. *
  435. * @see Symfony\Component\Finder\Iterator\SortableIterator
  436. *
  437. * @api
  438. */
  439. public function sort(\Closure $closure)
  440. {
  441. $this->sort = $closure;
  442. return $this;
  443. }
  444. /**
  445. * Sorts files and directories by name.
  446. *
  447. * This can be slow as all the matching files and directories must be retrieved for comparison.
  448. *
  449. * @return Finder The current Finder instance
  450. *
  451. * @see Symfony\Component\Finder\Iterator\SortableIterator
  452. *
  453. * @api
  454. */
  455. public function sortByName()
  456. {
  457. $this->sort = Iterator\SortableIterator::SORT_BY_NAME;
  458. return $this;
  459. }
  460. /**
  461. * Sorts files and directories by type (directories before files), then by name.
  462. *
  463. * This can be slow as all the matching files and directories must be retrieved for comparison.
  464. *
  465. * @return Finder The current Finder instance
  466. *
  467. * @see Symfony\Component\Finder\Iterator\SortableIterator
  468. *
  469. * @api
  470. */
  471. public function sortByType()
  472. {
  473. $this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
  474. return $this;
  475. }
  476. /**
  477. * Sorts files and directories by the last accessed time.
  478. *
  479. * This is the time that the file was last accessed, read or written to.
  480. *
  481. * This can be slow as all the matching files and directories must be retrieved for comparison.
  482. *
  483. * @return Finder The current Finder instance
  484. *
  485. * @see Symfony\Component\Finder\Iterator\SortableIterator
  486. *
  487. * @api
  488. */
  489. public function sortByAccessedTime()
  490. {
  491. $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
  492. return $this;
  493. }
  494. /**
  495. * Sorts files and directories by the last inode changed time.
  496. *
  497. * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
  498. *
  499. * On Windows, since inode is not available, changed time is actually the file creation time.
  500. *
  501. * This can be slow as all the matching files and directories must be retrieved for comparison.
  502. *
  503. * @return Finder The current Finder instance
  504. *
  505. * @see Symfony\Component\Finder\Iterator\SortableIterator
  506. *
  507. * @api
  508. */
  509. public function sortByChangedTime()
  510. {
  511. $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
  512. return $this;
  513. }
  514. /**
  515. * Sorts files and directories by the last modified time.
  516. *
  517. * This is the last time the actual contents of the file were last modified.
  518. *
  519. * This can be slow as all the matching files and directories must be retrieved for comparison.
  520. *
  521. * @return Finder The current Finder instance
  522. *
  523. * @see Symfony\Component\Finder\Iterator\SortableIterator
  524. *
  525. * @api
  526. */
  527. public function sortByModifiedTime()
  528. {
  529. $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
  530. return $this;
  531. }
  532. /**
  533. * Filters the iterator with an anonymous function.
  534. *
  535. * The anonymous function receives a \SplFileInfo and must return false
  536. * to remove files.
  537. *
  538. * @param \Closure $closure An anonymous function
  539. *
  540. * @return Finder The current Finder instance
  541. *
  542. * @see Symfony\Component\Finder\Iterator\CustomFilterIterator
  543. *
  544. * @api
  545. */
  546. public function filter(\Closure $closure)
  547. {
  548. $this->filters[] = $closure;
  549. return $this;
  550. }
  551. /**
  552. * Forces the following of symlinks.
  553. *
  554. * @return Finder The current Finder instance
  555. *
  556. * @api
  557. */
  558. public function followLinks()
  559. {
  560. $this->followLinks = true;
  561. return $this;
  562. }
  563. /**
  564. * Searches files and directories which match defined rules.
  565. *
  566. * @param string|array $dirs A directory path or an array of directories
  567. *
  568. * @return Finder The current Finder instance
  569. *
  570. * @throws \InvalidArgumentException if one of the directories does not exist
  571. *
  572. * @api
  573. */
  574. public function in($dirs)
  575. {
  576. $resolvedDirs = array();
  577. foreach ((array) $dirs as $dir) {
  578. if (is_dir($dir)) {
  579. $resolvedDirs[] = $dir;
  580. } elseif ($glob = glob($dir, GLOB_ONLYDIR)) {
  581. $resolvedDirs = array_merge($resolvedDirs, $glob);
  582. } else {
  583. throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir));
  584. }
  585. }
  586. $this->dirs = array_merge($this->dirs, $resolvedDirs);
  587. return $this;
  588. }
  589. /**
  590. * Returns an Iterator for the current Finder configuration.
  591. *
  592. * This method implements the IteratorAggregate interface.
  593. *
  594. * @return \Iterator An iterator
  595. *
  596. * @throws \LogicException if the in() method has not been called
  597. */
  598. public function getIterator()
  599. {
  600. if (0 === count($this->dirs) && 0 === count($this->iterators)) {
  601. throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
  602. }
  603. if (1 === count($this->dirs) && 0 === count($this->iterators)) {
  604. return $this->searchInDirectory($this->dirs[0]);
  605. }
  606. $iterator = new \AppendIterator();
  607. foreach ($this->dirs as $dir) {
  608. $iterator->append($this->searchInDirectory($dir));
  609. }
  610. foreach ($this->iterators as $it) {
  611. $iterator->append($it);
  612. }
  613. return $iterator;
  614. }
  615. /**
  616. * Appends an existing set of files/directories to the finder.
  617. *
  618. * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
  619. *
  620. * @param mixed $iterator
  621. *
  622. * @return Finder The finder
  623. *
  624. * @throws \InvalidArgumentException When the given argument is not iterable.
  625. */
  626. public function append($iterator)
  627. {
  628. if ($iterator instanceof \IteratorAggregate) {
  629. $this->iterators[] = $iterator->getIterator();
  630. } elseif ($iterator instanceof \Iterator) {
  631. $this->iterators[] = $iterator;
  632. } elseif ($iterator instanceof \Traversable || is_array($iterator)) {
  633. $it = new \ArrayIterator();
  634. foreach ($iterator as $file) {
  635. $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file));
  636. }
  637. $this->iterators[] = $it;
  638. } else {
  639. throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
  640. }
  641. return $this;
  642. }
  643. /**
  644. * Counts all the results collected by the iterators.
  645. *
  646. * @return int
  647. */
  648. public function count()
  649. {
  650. return iterator_count($this->getIterator());
  651. }
  652. /**
  653. * @return Finder The current Finder instance
  654. */
  655. private function sortAdapters()
  656. {
  657. uasort($this->adapters, function (array $a, array $b) {
  658. if ($a['selected'] || $b['selected']) {
  659. return $a['selected'] ? -1 : 1;
  660. }
  661. return $a['priority'] > $b['priority'] ? -1 : 1;
  662. });
  663. return $this;
  664. }
  665. /**
  666. * @param $dir
  667. *
  668. * @return \Iterator
  669. *
  670. * @throws \RuntimeException When none of the adapters are supported
  671. */
  672. private function searchInDirectory($dir)
  673. {
  674. if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
  675. $this->exclude = array_merge($this->exclude, self::$vcsPatterns);
  676. }
  677. if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
  678. $this->notPaths[] = '#(^|/)\..+(/|$)#';
  679. }
  680. foreach ($this->adapters as $adapter) {
  681. if ($adapter['adapter']->isSupported()) {
  682. try {
  683. return $this
  684. ->buildAdapter($adapter['adapter'])
  685. ->searchInDirectory($dir);
  686. } catch (ExceptionInterface $e) {}
  687. }
  688. }
  689. throw new \RuntimeException('No supported adapter found.');
  690. }
  691. /**
  692. * @param AdapterInterface $adapter
  693. *
  694. * @return AdapterInterface
  695. */
  696. private function buildAdapter(AdapterInterface $adapter)
  697. {
  698. return $adapter
  699. ->setFollowLinks($this->followLinks)
  700. ->setDepths($this->depths)
  701. ->setMode($this->mode)
  702. ->setExclude($this->exclude)
  703. ->setNames($this->names)
  704. ->setNotNames($this->notNames)
  705. ->setContains($this->contains)
  706. ->setNotContains($this->notContains)
  707. ->setSizes($this->sizes)
  708. ->setDates($this->dates)
  709. ->setFilters($this->filters)
  710. ->setSort($this->sort)
  711. ->setPath($this->paths)
  712. ->setNotPath($this->notPaths);
  713. }
  714. /**
  715. * Unselects all adapters.
  716. */
  717. private function resetAdapterSelection()
  718. {
  719. $this->adapters = array_map(function (array $properties) {
  720. $properties['selected'] = false;
  721. return $properties;
  722. }, $this->adapters);
  723. }
  724. }