AbstractFindAdapter.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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\Adapter;
  11. @trigger_error('The '.__NAMESPACE__.'\AbstractFindAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);
  12. use Symfony\Component\Finder\Comparator\DateComparator;
  13. use Symfony\Component\Finder\Comparator\NumberComparator;
  14. use Symfony\Component\Finder\Exception\AccessDeniedException;
  15. use Symfony\Component\Finder\Expression\Expression;
  16. use Symfony\Component\Finder\Iterator;
  17. use Symfony\Component\Finder\Shell\Command;
  18. use Symfony\Component\Finder\Shell\Shell;
  19. /**
  20. * Shell engine implementation using GNU find command.
  21. *
  22. * @author Jean-François Simon <contact@jfsimon.fr>
  23. *
  24. * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
  25. */
  26. abstract class AbstractFindAdapter extends AbstractAdapter
  27. {
  28. protected $shell;
  29. public function __construct()
  30. {
  31. $this->shell = new Shell();
  32. }
  33. /**
  34. * {@inheritdoc}
  35. */
  36. public function searchInDirectory($dir)
  37. {
  38. // having "/../" in path make find fail
  39. $dir = realpath($dir);
  40. // searching directories containing or not containing strings leads to no result
  41. if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
  42. return new Iterator\FilePathsIterator(array(), $dir);
  43. }
  44. $command = Command::create();
  45. $find = $this->buildFindCommand($command, $dir);
  46. if ($this->followLinks) {
  47. $find->add('-follow');
  48. }
  49. $find->add('-mindepth')->add($this->minDepth + 1);
  50. if (PHP_INT_MAX !== $this->maxDepth) {
  51. $find->add('-maxdepth')->add($this->maxDepth + 1);
  52. }
  53. if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
  54. $find->add('-type d');
  55. } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
  56. $find->add('-type f');
  57. }
  58. $this->buildNamesFiltering($find, $this->names);
  59. $this->buildNamesFiltering($find, $this->notNames, true);
  60. $this->buildPathsFiltering($find, $dir, $this->paths);
  61. $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
  62. $this->buildSizesFiltering($find, $this->sizes);
  63. $this->buildDatesFiltering($find, $this->dates);
  64. $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
  65. $useSort = \is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');
  66. if ($useGrep && ($this->contains || $this->notContains)) {
  67. $grep = $command->ins('grep');
  68. $this->buildContentFiltering($grep, $this->contains);
  69. $this->buildContentFiltering($grep, $this->notContains, true);
  70. }
  71. if ($useSort) {
  72. $this->buildSorting($command, $this->sort);
  73. }
  74. $command->setErrorHandler(
  75. $this->ignoreUnreadableDirs
  76. // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
  77. ? function ($stderr) { }
  78. : function ($stderr) { throw new AccessDeniedException($stderr); }
  79. );
  80. $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
  81. $iterator = new Iterator\FilePathsIterator($paths, $dir);
  82. if ($this->exclude) {
  83. $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
  84. }
  85. if (!$useGrep && ($this->contains || $this->notContains)) {
  86. $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
  87. }
  88. if ($this->filters) {
  89. $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
  90. }
  91. if (!$useSort && $this->sort) {
  92. $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
  93. $iterator = $iteratorAggregate->getIterator();
  94. }
  95. return $iterator;
  96. }
  97. /**
  98. * {@inheritdoc}
  99. */
  100. protected function canBeUsed()
  101. {
  102. return $this->shell->testCommand('find');
  103. }
  104. /**
  105. * @param Command $command
  106. * @param string $dir
  107. *
  108. * @return Command
  109. */
  110. protected function buildFindCommand(Command $command, $dir)
  111. {
  112. return $command
  113. ->ins('find')
  114. ->add('find ')
  115. ->arg($dir)
  116. ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
  117. }
  118. /**
  119. * @param Command $command
  120. * @param string[] $names
  121. * @param bool $not
  122. */
  123. private function buildNamesFiltering(Command $command, array $names, $not = false)
  124. {
  125. if (0 === \count($names)) {
  126. return;
  127. }
  128. $command->add($not ? '-not' : null)->cmd('(');
  129. foreach ($names as $i => $name) {
  130. $expr = Expression::create($name);
  131. // Find does not support expandable globs ("*.{a,b}" syntax).
  132. if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
  133. $expr = Expression::create($expr->getGlob()->toRegex(false));
  134. }
  135. // Fixes 'not search' and 'full path matching' regex problems.
  136. // - Jokers '.' are replaced by [^/].
  137. // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
  138. if ($expr->isRegex()) {
  139. $regex = $expr->getRegex();
  140. $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
  141. ->setStartFlag(false)
  142. ->setStartJoker(true)
  143. ->replaceJokers('[^/]');
  144. if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
  145. $regex->setEndJoker(false)->append('[^/]*');
  146. }
  147. }
  148. $command
  149. ->add($i > 0 ? '-or' : null)
  150. ->add($expr->isRegex()
  151. ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
  152. : ($expr->isCaseSensitive() ? '-name' : '-iname')
  153. )
  154. ->arg($expr->renderPattern());
  155. }
  156. $command->cmd(')');
  157. }
  158. /**
  159. * @param Command $command
  160. * @param string $dir
  161. * @param string[] $paths
  162. * @param bool $not
  163. */
  164. private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
  165. {
  166. if (0 === \count($paths)) {
  167. return;
  168. }
  169. $command->add($not ? '-not' : null)->cmd('(');
  170. foreach ($paths as $i => $path) {
  171. $expr = Expression::create($path);
  172. // Find does not support expandable globs ("*.{a,b}" syntax).
  173. if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
  174. $expr = Expression::create($expr->getGlob()->toRegex(false));
  175. }
  176. // Fixes 'not search' regex problems.
  177. if ($expr->isRegex()) {
  178. $regex = $expr->getRegex();
  179. $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).\DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
  180. } else {
  181. $expr->prepend('*')->append('*');
  182. }
  183. $command
  184. ->add($i > 0 ? '-or' : null)
  185. ->add($expr->isRegex()
  186. ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
  187. : ($expr->isCaseSensitive() ? '-path' : '-ipath')
  188. )
  189. ->arg($expr->renderPattern());
  190. }
  191. $command->cmd(')');
  192. }
  193. /**
  194. * @param Command $command
  195. * @param NumberComparator[] $sizes
  196. */
  197. private function buildSizesFiltering(Command $command, array $sizes)
  198. {
  199. foreach ($sizes as $i => $size) {
  200. $command->add($i > 0 ? '-and' : null);
  201. switch ($size->getOperator()) {
  202. case '<=':
  203. $command->add('-size -'.($size->getTarget() + 1).'c');
  204. break;
  205. case '>=':
  206. $command->add('-size +'.($size->getTarget() - 1).'c');
  207. break;
  208. case '>':
  209. $command->add('-size +'.$size->getTarget().'c');
  210. break;
  211. case '!=':
  212. $command->add('-size -'.$size->getTarget().'c');
  213. $command->add('-size +'.$size->getTarget().'c');
  214. break;
  215. case '<':
  216. default:
  217. $command->add('-size -'.$size->getTarget().'c');
  218. }
  219. }
  220. }
  221. /**
  222. * @param Command $command
  223. * @param DateComparator[] $dates
  224. */
  225. private function buildDatesFiltering(Command $command, array $dates)
  226. {
  227. foreach ($dates as $i => $date) {
  228. $command->add($i > 0 ? '-and' : null);
  229. $mins = (int) round((time() - $date->getTarget()) / 60);
  230. if (0 > $mins) {
  231. // mtime is in the future
  232. $command->add(' -mmin -0');
  233. // we will have no result so we don't need to continue
  234. return;
  235. }
  236. switch ($date->getOperator()) {
  237. case '<=':
  238. $command->add('-mmin +'.($mins - 1));
  239. break;
  240. case '>=':
  241. $command->add('-mmin -'.($mins + 1));
  242. break;
  243. case '>':
  244. $command->add('-mmin -'.$mins);
  245. break;
  246. case '!=':
  247. $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
  248. break;
  249. case '<':
  250. default:
  251. $command->add('-mmin +'.$mins);
  252. }
  253. }
  254. }
  255. /**
  256. * @param Command $command
  257. * @param string $sort
  258. *
  259. * @throws \InvalidArgumentException
  260. */
  261. private function buildSorting(Command $command, $sort)
  262. {
  263. $this->buildFormatSorting($command, $sort);
  264. }
  265. /**
  266. * @param Command $command
  267. * @param string $sort
  268. */
  269. abstract protected function buildFormatSorting(Command $command, $sort);
  270. /**
  271. * @param Command $command
  272. * @param array $contains
  273. * @param bool $not
  274. */
  275. abstract protected function buildContentFiltering(Command $command, array $contains, $not = false);
  276. }