TYPO3  7.6
ClassMapGenerator.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is copied from the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  *
11  * @license MIT
12  */
13 
14 namespace Composer\Autoload;
15 
17 use Composer\IO\IOInterface;
18 
23 {
30  public static function dump($dirs, $file)
31  {
32  $maps = array();
33 
34  foreach ($dirs as $dir) {
35  $maps = array_merge($maps, static::createMap($dir));
36  }
37 
38  file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
39  }
40 
53  public static function createMap($path, $whitelist = null, IOInterface $io = null, $namespace = null)
54  {
55  if (is_string($path)) {
56  if (is_file($path)) {
57  $path = array(new \SplFileInfo($path));
58  } elseif (is_dir($path)) {
59  $path = Finder::create()->files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path);
60  } else {
61  throw new \RuntimeException(
62  'Could not scan for classes inside "' . $path .
63  '" which does not appear to be a file nor a folder'
64  );
65  }
66  }
67 
68  $map = array();
69 
70  foreach ($path as $file) {
71  $filePath = $file->getRealPath();
72 
73  if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), array('php', 'inc', 'hh'))) {
74  continue;
75  }
76 
77  if ($whitelist && !preg_match($whitelist, strtr($filePath, '\\', '/'))) {
78  continue;
79  }
80 
81  $classes = self::findClasses($filePath);
82 
83  foreach ($classes as $class) {
84  // skip classes not within the given namespace prefix
85  if (null !== $namespace && 0 !== strpos($class, $namespace)) {
86  continue;
87  }
88 
89  if (!isset($map[$class])) {
90  $map[$class] = $filePath;
91  } elseif ($io && $map[$class] !== $filePath && !preg_match('{/(test|fixture|example)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) {
92  $io->writeError(
93  '<warning>Warning: Ambiguous class resolution, "' . $class . '"' .
94  ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.</warning>'
95  );
96  }
97  }
98  }
99 
100  return $map;
101  }
102 
110  private static function findClasses($path)
111  {
112  $extraTypes = PHP_VERSION_ID < 50400 ? '' : '|trait';
113  if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) {
114  $extraTypes .= '|enum';
115  }
116 
117  try {
118  $contents = @php_strip_whitespace($path);
119  if (!$contents) {
120  if (!file_exists($path)) {
121  throw new \Exception('File does not exist');
122  }
123  if (!is_readable($path)) {
124  throw new \Exception('File is not readable');
125  }
126  }
127  } catch (\Exception $e) {
128  throw new \RuntimeException('Could not scan for classes inside ' . $path . ": \n" . $e->getMessage(), 0, $e);
129  }
130 
131  // return early if there is no chance of matching anything in this file
132  if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) {
133  return array();
134  }
135 
136  // strip heredocs/nowdocs
137  $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents);
138  // strip strings
139  $contents = preg_replace('{"[^"\\\\]*(\\\\.[^"\\\\]*)*"|\'[^\'\\\\]*(\\\\.[^\'\\\\]*)*\'}s', 'null', $contents);
140  // strip leading non-php code if needed
141  if (substr($contents, 0, 2) !== '<?') {
142  $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
143  if ($replacements === 0) {
144  return array();
145  }
146  }
147  // strip non-php blocks in the file
148  $contents = preg_replace('{\?>.+<\?}s', '?><?', $contents);
149  // strip trailing non-php code if needed
150  $pos = strrpos($contents, '?>');
151  if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
152  $contents = substr($contents, 0, $pos);
153  }
154 
155  preg_match_all('{
156  (?:
157  \b(?<![\$:>])(?P<type>class|interface' . $extraTypes . ') \s+ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*)
158  | \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*[\{;]
159  )
160  }ix', $contents, $matches);
161 
162  $classes = array();
163  $namespace = '';
164 
165  for ($i = 0, $len = count($matches['type']); $i < $len; $i++) {
166  if (!empty($matches['ns'][$i])) {
167  $namespace = str_replace(array(' ', "\t", "\r", "\n"), '', $matches['nsname'][$i]) . '\\';
168  } else {
169  $name = $matches['name'][$i];
170  if ($name[0] === ':') {
171  // This is an XHP class, https://github.com/facebook/xhp
172  $name = 'xhp' . substr(str_replace(array('-', ':'), array('_', '__'), $name), 1);
173  } elseif ($matches['type'][$i] === 'enum') {
174  // In Hack, something like:
175  // enum Foo: int { HERP = '123'; }
176  // The regex above captures the colon, which isn't part of
177  // the class name.
178  $name = rtrim($name, ':');
179  }
180  $classes[] = ltrim($namespace . $name, '\\');
181  }
182  }
183 
184  return $classes;
185  }
186 }