ClassMapGenerator.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. <?php
  2. /*
  3. * This file is copied from 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. * @license MIT
  11. */
  12. namespace Composer\Autoload;
  13. /**
  14. * ClassMapGenerator
  15. *
  16. * @author Gyula Sallai <salla016@gmail.com>
  17. */
  18. class ClassMapGenerator
  19. {
  20. /**
  21. * Generate a class map file
  22. *
  23. * @param Traversable $dirs Directories or a single path to search in
  24. * @param string $file The name of the class map file
  25. */
  26. public static function dump($dirs, $file)
  27. {
  28. $maps = array();
  29. foreach ($dirs as $dir) {
  30. $maps = array_merge($maps, static::createMap($dir));
  31. }
  32. file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
  33. }
  34. /**
  35. * Iterate over all files in the given directory searching for classes
  36. *
  37. * @param Iterator|string $path The path to search in or an iterator
  38. * @param string $whitelist Regex that matches against the file path
  39. *
  40. * @return array A class map array
  41. *
  42. * @throws \RuntimeException When the path is neither an existing file nor directory
  43. */
  44. public static function createMap($path, $whitelist = null)
  45. {
  46. if (is_string($path)) {
  47. if (is_file($path)) {
  48. $path = array(new \SplFileInfo($path));
  49. } else if (is_dir($path)) {
  50. $path = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path));
  51. } else {
  52. throw new \RuntimeException(
  53. 'Could not scan for classes inside "'.$path.
  54. '" which does not appear to be a file nor a folder'
  55. );
  56. }
  57. }
  58. $map = array();
  59. foreach ($path as $file) {
  60. if (!$file->isFile()) {
  61. continue;
  62. }
  63. $filePath = $file->getRealPath();
  64. if (pathinfo($filePath, PATHINFO_EXTENSION) !== 'php') {
  65. continue;
  66. }
  67. if ($whitelist && !preg_match($whitelist, strtr($filePath, '\\', '/'))) {
  68. continue;
  69. }
  70. $classes = self::findClasses($filePath);
  71. foreach ($classes as $class) {
  72. $map[$class] = $filePath;
  73. }
  74. }
  75. return $map;
  76. }
  77. /**
  78. * Extract the classes in the given file
  79. *
  80. * @param string $path The file to check
  81. *
  82. * @return array The found classes
  83. */
  84. private static function findClasses($path)
  85. {
  86. $traits = version_compare(PHP_VERSION, '5.4', '<') ? '' : '|trait';
  87. try {
  88. $contents = php_strip_whitespace($path);
  89. } catch (\Exception $e) {
  90. throw new \RuntimeException('Could not scan for classes inside '.$path.": \n".$e->getMessage(), 0, $e);
  91. }
  92. // return early if there is no chance of matching anything in this file
  93. if (!preg_match('{\b(?:class|interface'.$traits.')\b}i', $contents)) {
  94. return array();
  95. }
  96. // strip heredocs/nowdocs
  97. $contents = preg_replace('{<<<\'?(\w+)\'?(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\1(?=\r\n|\n|\r|;)}s', 'null', $contents);
  98. // strip strings
  99. $contents = preg_replace('{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}', 'null', $contents);
  100. // strip leading non-php code if needed
  101. if (substr($contents, 0, 2) !== '<?') {
  102. $contents = preg_replace('{^.+?<\?}s', '<?', $contents);
  103. }
  104. // strip non-php blocks in the file
  105. $contents = preg_replace('{\?>.*<\?}s', '', $contents);
  106. preg_match_all('{
  107. (?:
  108. \b(?<![\$:>])(?<type>class|interface'.$traits.') \s+ (?<name>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)
  109. | \b(?<![\$:>])(?<ns>namespace) (?<nsname>\s+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\s*\\\\\s*[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*)? \s*[\{;]
  110. )
  111. }ix', $contents, $matches);
  112. $classes = array();
  113. $namespace = '';
  114. for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
  115. if (!empty($matches['ns'][$i])) {
  116. $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\';
  117. } else {
  118. $classes[] = ltrim($namespace . $matches['name'][$i], '\\');
  119. }
  120. }
  121. return $classes;
  122. }
  123. }