PatternFinder.php 5.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. /*
  3. * Copyright 2011 Johannes M. Schmitt <schmittjoh@gmail.com>
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. namespace JMS\DiExtraBundle\Finder;
  18. use JMS\DiExtraBundle\Exception\RuntimeException;
  19. use Symfony\Component\Finder\Finder;
  20. use Symfony\Component\Process\ExecutableFinder;
  21. class PatternFinder
  22. {
  23. const METHOD_GREP = 1;
  24. const METHOD_FINDSTR = 2;
  25. const METHOD_FINDER = 3;
  26. private static $method;
  27. private static $grepPath;
  28. private $pattern;
  29. private $filePattern;
  30. private $recursive = true;
  31. private $regexPattern = false;
  32. public function __construct($pattern, $filePattern = '*.php')
  33. {
  34. if (null === self::$method) {
  35. self::determineMethod();
  36. }
  37. $this->pattern = $pattern;
  38. $this->filePattern = $filePattern;
  39. }
  40. public function setRecursive($bool)
  41. {
  42. $this->recursive = (Boolean) $bool;
  43. }
  44. public function setRegexPattern($bool)
  45. {
  46. $this->regexPattern = (Boolean) $bool;
  47. }
  48. public function findFiles(array $dirs)
  49. {
  50. // check for grep availability
  51. if (self::METHOD_GREP === self::$method) {
  52. return $this->findUsingGrep($dirs);
  53. }
  54. // use FINDSTR on Windows
  55. if (self::METHOD_FINDSTR === self::$method) {
  56. return $this->findUsingFindstr($dirs);
  57. }
  58. // this should really be avoided at all costs since it is damn slow
  59. return $this->findUsingFinder($dirs);
  60. }
  61. private function findUsingFindstr(array $dirs)
  62. {
  63. $cmd = 'FINDSTR /M /S /P';
  64. if (!$this->recursive) {
  65. $cmd .= ' /L';
  66. }
  67. $cmd .= ' /D:'.escapeshellarg(implode(';', $dirs));
  68. $cmd .= ' '.escapeshellarg($this->pattern);
  69. $cmd .= ' '.$this->filePattern;
  70. exec($cmd, $lines, $exitCode);
  71. if (1 === $exitCode) {
  72. return array();
  73. }
  74. if (0 !== $exitCode) {
  75. throw new RuntimeException(sprintf('Command "%s" exited with non-successful status code. "%d".', $cmd, $exitCode));
  76. }
  77. // Looks like FINDSTR has different versions with different output formats.
  78. //
  79. // Supported format #1:
  80. // C:\matched\dir1:
  81. // Relative\Path\To\File1.php
  82. // Relative\Path\To\File2.php
  83. // C:\matched\dir2:
  84. // Relative\Path\To\File3.php
  85. // Relative\Path\To\File4.php
  86. //
  87. // Supported format #2:
  88. // C:\matched\dir1\Relative\Path\To\File1.php
  89. // C:\matched\dir1\Relative\Path\To\File2.php
  90. // C:\matched\dir2\Relative\Path\To\File3.php
  91. // C:\matched\dir2\Relative\Path\To\File4.php
  92. $files = array();
  93. $currentDir = '';
  94. foreach ($lines as $line) {
  95. if (':' === substr($line, -1)) {
  96. $currentDir = trim($line, ' :/').'/';
  97. continue;
  98. }
  99. $files[] = $currentDir.$line;
  100. }
  101. return $files;
  102. }
  103. private function findUsingGrep(array $dirs)
  104. {
  105. $cmd = self::$grepPath;
  106. if (!$this->regexPattern) {
  107. $cmd .= ' --fixed-strings';
  108. } else {
  109. $cmd .= ' --extended-regexp';
  110. }
  111. if ($this->recursive) {
  112. $cmd .= ' --directories=recurse';
  113. } else {
  114. $cmd .= ' --directories=skip';
  115. }
  116. $cmd .= ' --devices=skip --files-with-matches --with-filename --color=never --include='.$this->filePattern;
  117. $cmd .= ' '.escapeshellarg($this->pattern);
  118. foreach ($dirs as $dir) {
  119. $cmd .= ' '.escapeshellarg($dir);
  120. }
  121. exec($cmd, $files, $exitCode);
  122. if (1 === $exitCode) {
  123. return array();
  124. }
  125. if (0 !== $exitCode) {
  126. throw new RuntimeException(sprintf('Command "%s" exited with non-successful status code "%d".', $cmd, $exitCode));
  127. }
  128. return $files;
  129. }
  130. private function findUsingFinder(array $dirs)
  131. {
  132. $finder = new Finder();
  133. $pattern = $this->pattern;
  134. $regex = $this->regexPattern;
  135. $finder
  136. ->files()
  137. ->name($this->filePattern)
  138. ->in($dirs)
  139. ->ignoreVCS(true)
  140. ->filter(function($file) use ($pattern, $regex) {
  141. if (!$regex) {
  142. return false !== strpos(file_get_contents($file->getPathName()), $pattern);
  143. }
  144. return 0 < preg_match('#'.$pattern.'#', file_get_contents($file->getPathName()));
  145. })
  146. ;
  147. if (!$this->recursive) {
  148. $finder->depth('<= 0');
  149. }
  150. return array_keys(iterator_to_array($finder));
  151. }
  152. private static function determineMethod()
  153. {
  154. $finder = new ExecutableFinder();
  155. $isWindows = 0 === stripos(PHP_OS, 'win');
  156. $execAvailable = function_exists('exec');
  157. if (!$isWindows && $execAvailable && self::$grepPath = $finder->find('grep')) {
  158. self::$method = self::METHOD_GREP;
  159. } else if ($isWindows && $execAvailable) {
  160. self::$method = self::METHOD_FINDSTR;
  161. } else {
  162. self::$method = self::METHOD_FINDER;
  163. }
  164. }
  165. }