ClassMapGenerator.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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 $dir The directory 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. public static function createMap($dir, $whitelist = null)
  43. {
  44. if (is_string($dir)) {
  45. if (is_file($dir)) {
  46. $dir = array(new \SplFileInfo($dir));
  47. } else {
  48. $dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir));
  49. }
  50. }
  51. $map = array();
  52. foreach ($dir as $file) {
  53. if (!$file->isFile()) {
  54. continue;
  55. }
  56. $path = $file->getRealPath();
  57. if (pathinfo($path, PATHINFO_EXTENSION) !== 'php') {
  58. continue;
  59. }
  60. if ($whitelist && !preg_match($whitelist, strtr($path, '\\', '/'))) {
  61. continue;
  62. }
  63. $classes = self::findClasses($path);
  64. foreach ($classes as $class) {
  65. $map[$class] = $path;
  66. }
  67. }
  68. return $map;
  69. }
  70. /**
  71. * Extract the classes in the given file
  72. *
  73. * @param string $path The file to check
  74. *
  75. * @return array The found classes
  76. */
  77. private static function findClasses($path)
  78. {
  79. $contents = php_strip_whitespace($path);
  80. try {
  81. if (!preg_match('{\b(?:class|interface|trait)\b}i', $contents)) {
  82. return array();
  83. }
  84. // strip heredocs/nowdocs
  85. $contents = preg_replace('{<<<\'?(\w+)\'?(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\1(?=\r\n|\n|\r|;)}s', 'null', $contents);
  86. // strip strings
  87. $contents = preg_replace('{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}', 'null', $contents);
  88. preg_match_all('{(?:\b(?<![\$:>])(?<type>class|interface|trait)\s+(?<name>\S+)|\b(?<![\$:>])(?<ns>namespace)\s+(?<nsname>[^\s;{}\\\\]+(?:\s*\\\\\s*[^\s;{}\\\\]+)*))}i', $contents, $matches);
  89. $classes = array();
  90. $namespace = '';
  91. for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
  92. $name = $matches['name'][$i];
  93. if (!empty($matches['ns'][$i])) {
  94. $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\';
  95. } else {
  96. $classes[] = ltrim($namespace . $matches['name'][$i], '\\');
  97. }
  98. }
  99. return $classes;
  100. } catch (\Exception $e) {
  101. throw new \RuntimeException('Could not scan for classes inside '.$path.": \n".$e->getMessage(), 0, $e);
  102. }
  103. }
  104. }