21 use Composer\IO\IOInterface;
22 use Symfony\Component\Finder\Finder;
30 class ClassMapGenerator
38 public static function dump($dirs, $file)
42 foreach ($dirs as
$dir) {
43 $maps = array_merge($maps, static::createMap(
$dir));
46 file_put_contents($file, sprintf(
'<?php return %s;', var_export($maps,
true)));
60 public static function createMap($path, $blacklist =
null, IOInterface $io =
null, $namespace =
null)
62 if (is_string($path)) {
64 $path = [new \SplFileInfo($path)];
65 } elseif (is_dir($path)) {
66 $path = Finder::create()->files()->followLinks()->name(
'/\.(php|inc|hh)$/')->in($path);
68 throw new \RuntimeException(
69 'Could not scan for classes inside "' . $path .
70 '" which does not appear to be a file nor a folder',
78 foreach ($path as $file) {
79 $filePath = $file->getRealPath();
81 if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), [
'php',
'inc',
'hh'])) {
85 if ($blacklist && preg_match($blacklist, str_replace(
'\\',
'/', $filePath))) {
89 $classes = self::findClasses($filePath);
91 foreach ($classes as $class) {
93 if (
null !== $namespace && 0 !== strpos($class, $namespace)) {
97 if (!isset($map[$class])) {
98 $map[$class] = $filePath;
99 } elseif ($io && $map[$class] !== $filePath && !preg_match(
100 '{/(test|fixture|example|stub)s?/}i',
101 str_replace(
'\\',
'/', $map[$class] .
' ' . $filePath)
104 '<warning>Warning: Ambiguous class resolution, "' . $class .
'"' .
105 ' was found in both "' . $map[$class] .
'" and "' . $filePath .
'", the first will be used.</warning>'
121 private static function findClasses($path)
123 $extraTypes = PHP_VERSION_ID < 50400 ?
'' :
'|trait';
124 if (defined(
'HHVM_VERSION') && version_compare(HHVM_VERSION,
'3.3',
'>=')) {
125 $extraTypes .=
'|enum';
129 $contents = @php_strip_whitespace($path);
131 if (!file_exists($path)) {
132 throw new \Exception(
'File does not exist', 1476049981);
134 if (!is_readable($path)) {
135 throw new \Exception(
'File is not readable', 1476049990);
138 }
catch (\Exception $e) {
139 throw new \RuntimeException(
'Could not scan for classes inside ' . $path .
": \n" . $e->getMessage(), 1476050009, $e);
143 if (!preg_match(
'{\b(?:class|interface' . $extraTypes .
')\s}i', $contents)) {
148 $contents = preg_replace(
'{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s',
'null', $contents);
150 $contents = preg_replace(
'{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s',
'null', $contents);
152 if (strpos($contents,
'<?') !== 0) {
153 $contents = preg_replace(
'{^.+?<\?}s',
'<?', $contents, 1, $replacements);
154 if ($replacements === 0) {
159 $contents = preg_replace(
'{\?>.+<\?}s',
'?><?', $contents);
161 $pos = strrpos($contents,
'?>');
162 if (
false !== $pos &&
false === strpos(substr($contents, $pos),
'<?')) {
163 $contents = substr($contents, 0, $pos);
168 \b(?<![\$:>])(?P<type>class|interface' . $extraTypes .
') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
169 | \b(?<![\$:>])(?P<ns>namespace) (?P<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*+ [\{;]
171 }ix', $contents, $matches);
176 $typeCount = count($matches[
'type']);
177 for ($i = 0, $len = $typeCount; $i < $len; $i++) {
178 if (!empty($matches[
'ns'][$i])) {
179 $namespace = str_replace([
' ',
"\t",
"\r",
"\n"],
'', $matches[
'nsname'][$i]) .
'\\';
181 $name = $matches[
'name'][$i];
182 if ($name[0] ===
':') {
184 $name =
'xhp' . substr(str_replace([
'-',
':'], [
'_',
'__'], $name), 1);
185 } elseif ($matches[
'type'][$i] ===
'enum') {
190 $name = rtrim($name,
':');
192 $classes[] = ltrim($namespace . $name,
'\\');