4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\Finder\Adapter;
14 @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);
16 use Symfony\Component\Finder\Exception\AccessDeniedException;
17 use Symfony\Component\Finder\Iterator;
18 use Symfony\Component\Finder\Shell\Shell;
19 use Symfony\Component\Finder\Expression\Expression;
20 use Symfony\Component\Finder\Shell\Command;
21 use Symfony\Component\Finder\Comparator\NumberComparator;
22 use Symfony\Component\Finder\Comparator\DateComparator;
25 * Shell engine implementation using GNU find command.
27 * @author Jean-François Simon <contact@jfsimon.fr>
29 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
31 abstract class AbstractFindAdapter extends AbstractAdapter
35 public function __construct()
37 $this->shell = new Shell();
43 public function searchInDirectory($dir)
45 // having "/../" in path make find fail
46 $dir = realpath($dir);
48 // searching directories containing or not containing strings leads to no result
49 if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
50 return new Iterator\FilePathsIterator(array(), $dir);
53 $command = Command::create();
54 $find = $this->buildFindCommand($command, $dir);
56 if ($this->followLinks) {
57 $find->add('-follow');
60 $find->add('-mindepth')->add($this->minDepth + 1);
62 if (PHP_INT_MAX !== $this->maxDepth) {
63 $find->add('-maxdepth')->add($this->maxDepth + 1);
66 if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
67 $find->add('-type d');
68 } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
69 $find->add('-type f');
72 $this->buildNamesFiltering($find, $this->names);
73 $this->buildNamesFiltering($find, $this->notNames, true);
74 $this->buildPathsFiltering($find, $dir, $this->paths);
75 $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
76 $this->buildSizesFiltering($find, $this->sizes);
77 $this->buildDatesFiltering($find, $this->dates);
79 $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
80 $useSort = is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');
82 if ($useGrep && ($this->contains || $this->notContains)) {
83 $grep = $command->ins('grep');
84 $this->buildContentFiltering($grep, $this->contains);
85 $this->buildContentFiltering($grep, $this->notContains, true);
89 $this->buildSorting($command, $this->sort);
92 $command->setErrorHandler(
93 $this->ignoreUnreadableDirs
94 // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
95 ? function ($stderr) { }
96 : function ($stderr) { throw new AccessDeniedException($stderr); }
99 $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
100 $iterator = new Iterator\FilePathsIterator($paths, $dir);
102 if ($this->exclude) {
103 $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
106 if (!$useGrep && ($this->contains || $this->notContains)) {
107 $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
110 if ($this->filters) {
111 $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
114 if (!$useSort && $this->sort) {
115 $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
116 $iterator = $iteratorAggregate->getIterator();
125 protected function canBeUsed()
127 return $this->shell->testCommand('find');
131 * @param Command $command
136 protected function buildFindCommand(Command $command, $dir)
142 ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
146 * @param Command $command
147 * @param string[] $names
150 private function buildNamesFiltering(Command $command, array $names, $not = false)
152 if (0 === count($names)) {
156 $command->add($not ? '-not' : null)->cmd('(');
158 foreach ($names as $i => $name) {
159 $expr = Expression::create($name);
161 // Find does not support expandable globs ("*.{a,b}" syntax).
162 if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
163 $expr = Expression::create($expr->getGlob()->toRegex(false));
166 // Fixes 'not search' and 'full path matching' regex problems.
167 // - Jokers '.' are replaced by [^/].
168 // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
169 if ($expr->isRegex()) {
170 $regex = $expr->getRegex();
171 $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
172 ->setStartFlag(false)
173 ->setStartJoker(true)
174 ->replaceJokers('[^/]');
175 if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
176 $regex->setEndJoker(false)->append('[^/]*');
181 ->add($i > 0 ? '-or' : null)
182 ->add($expr->isRegex()
183 ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
184 : ($expr->isCaseSensitive() ? '-name' : '-iname')
186 ->arg($expr->renderPattern());
193 * @param Command $command
195 * @param string[] $paths
198 private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
200 if (0 === count($paths)) {
204 $command->add($not ? '-not' : null)->cmd('(');
206 foreach ($paths as $i => $path) {
207 $expr = Expression::create($path);
209 // Find does not support expandable globs ("*.{a,b}" syntax).
210 if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
211 $expr = Expression::create($expr->getGlob()->toRegex(false));
214 // Fixes 'not search' regex problems.
215 if ($expr->isRegex()) {
216 $regex = $expr->getRegex();
217 $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
219 $expr->prepend('*')->append('*');
223 ->add($i > 0 ? '-or' : null)
224 ->add($expr->isRegex()
225 ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
226 : ($expr->isCaseSensitive() ? '-path' : '-ipath')
228 ->arg($expr->renderPattern());
235 * @param Command $command
236 * @param NumberComparator[] $sizes
238 private function buildSizesFiltering(Command $command, array $sizes)
240 foreach ($sizes as $i => $size) {
241 $command->add($i > 0 ? '-and' : null);
243 switch ($size->getOperator()) {
245 $command->add('-size -'.($size->getTarget() + 1).'c');
248 $command->add('-size +'.($size->getTarget() - 1).'c');
251 $command->add('-size +'.$size->getTarget().'c');
254 $command->add('-size -'.$size->getTarget().'c');
255 $command->add('-size +'.$size->getTarget().'c');
259 $command->add('-size -'.$size->getTarget().'c');
265 * @param Command $command
266 * @param DateComparator[] $dates
268 private function buildDatesFiltering(Command $command, array $dates)
270 foreach ($dates as $i => $date) {
271 $command->add($i > 0 ? '-and' : null);
273 $mins = (int) round((time() - $date->getTarget()) / 60);
276 // mtime is in the future
277 $command->add(' -mmin -0');
278 // we will have no result so we don't need to continue
282 switch ($date->getOperator()) {
284 $command->add('-mmin +'.($mins - 1));
287 $command->add('-mmin -'.($mins + 1));
290 $command->add('-mmin -'.$mins);
293 $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
297 $command->add('-mmin +'.$mins);
303 * @param Command $command
304 * @param string $sort
306 * @throws \InvalidArgumentException
308 private function buildSorting(Command $command, $sort)
310 $this->buildFormatSorting($command, $sort);
314 * @param Command $command
315 * @param string $sort
317 abstract protected function buildFormatSorting(Command $command, $sort);
320 * @param Command $command
321 * @param array $contains
324 abstract protected function buildContentFiltering(Command $command, array $contains, $not = false);