‪TYPO3CMS  11.5
ClassMapGenerator.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of Composer.
5  *
6  * (c) Nils Adermann <naderman@naderman.de>
7  * Jordi Boggiano <j.boggiano@seld.be>
8  *
9  * For the full copyright and license information, please view the LICENSE
10  * file that was distributed with this source code.
11  */
12 
13 /*
14  * This file is copied from the Symfony package.
15  *
16  * (c) Fabien Potencier <fabien@symfony.com>
17  */
18 
20 
21 use Composer\IO\IOInterface;
22 use Symfony\Component\Finder\Finder;
23 
30 class ClassMapGenerator
31 {
38  public static function dump($dirs, $file)
39  {
40  $maps = [];
41 
42  foreach ($dirs as ‪$dir) {
43  $maps = array_merge($maps, static::createMap(‪$dir));
44  }
45 
46  file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
47  }
48 
60  public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null)
61  {
62  if (is_string($path)) {
63  if (is_file($path)) {
64  $path = [new \SplFileInfo($path)];
65  } elseif (is_dir($path)) {
66  $path = Finder::create()->files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path);
67  } else {
68  throw new \RuntimeException(
69  'Could not scan for classes inside "' . $path .
70  '" which does not appear to be a file nor a folder',
71  1476049953
72  );
73  }
74  }
75 
76  $map = [];
77 
78  foreach ($path as $file) {
79  $filePath = $file->getRealPath();
80 
81  if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), ['php', 'inc', 'hh'])) {
82  continue;
83  }
84 
85  if ($blacklist && preg_match($blacklist, str_replace('\\', '/', $filePath))) {
86  continue;
87  }
88 
89  $classes = self::findClasses($filePath);
90 
91  foreach ($classes as $class) {
92  // skip classes not within the given namespace prefix
93  if ($namespace !== null && strpos($class, $namespace) !== 0) {
94  continue;
95  }
96 
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)
102  )) {
103  $io->writeError(
104  '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
105  ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.</warning>'
106  );
107  }
108  }
109  }
110 
111  return $map;
112  }
113 
121  private static function findClasses($path)
122  {
123  $extraTypes = PHP_VERSION_ID < 50400 ? '' : '|trait';
124  if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) {
125  $extraTypes .= '|enum';
126  }
127 
128  try {
129  $contents = @php_strip_whitespace($path);
130  if (!$contents) {
131  if (!file_exists($path)) {
132  throw new \Exception('File does not exist', 1476049981);
133  }
134  if (!is_readable($path)) {
135  throw new \Exception('File is not readable', 1476049990);
136  }
137  }
138  } catch (\Exception $e) {
139  throw new \RuntimeException('Could not scan for classes inside ' . $path . ": \n" . $e->getMessage(), 1476050009, $e);
140  }
141 
142  // return early if there is no chance of matching anything in this file
143  if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) {
144  return [];
145  }
146 
147  // strip heredocs/nowdocs
148  $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
149  // strip strings
150  $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
151  // strip leading non-php code if needed
152  if (strpos($contents, '<?') !== 0) {
153  $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
154  if ($replacements === 0) {
155  return [];
156  }
157  }
158  // strip non-php blocks in the file
159  $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
160  // strip trailing non-php code if needed
161  $pos = strrpos($contents, '?>');
162  if ($pos !== false && !str_contains(substr($contents, $pos), '<?')) {
163  $contents = substr($contents, 0, $pos);
164  }
165 
166  preg_match_all('{
167  (?:
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*+ [\{;]
170  )
171  }ix', $contents, $matches);
172 
173  $classes = [];
174  $namespace = '';
175 
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]) . '\\';
180  } else {
181  $name = $matches['name'][$i];
182  if ($name[0] === ':') {
183  // This is an XHP class, https://github.com/facebook/xhp
184  $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
185  } elseif ($matches['type'][$i] === 'enum') {
186  // In Hack, something like:
187  // enum Foo: int { HERP = '123'; }
188  // The regex above captures the colon, which isn't part of
189  // the class name.
190  $name = rtrim($name, ':');
191  }
192  $classes[] = ltrim($namespace . $name, '\\');
193  }
194  }
195 
196  return $classes;
197  }
198 }
‪$dir
‪$dir
Definition: validateRstFiles.php:213
‪Composer\Autoload
Definition: ClassMapGenerator.php:19