Chris@17
|
1 <?php
|
Chris@17
|
2 /**
|
Chris@17
|
3 * Autoloads files for PHP_CodeSniffer and tracks what has been loaded.
|
Chris@17
|
4 *
|
Chris@17
|
5 * Due to different namespaces being used for custom coding standards,
|
Chris@17
|
6 * the autoloader keeps track of what class is loaded after a file is included,
|
Chris@17
|
7 * even if the file is ultimately included by another autoloader (such as composer).
|
Chris@17
|
8 *
|
Chris@17
|
9 * This allows PHP_CodeSniffer to request the class name after loading a class
|
Chris@17
|
10 * when it only knows the filename, without having to parse the file to find it.
|
Chris@17
|
11 *
|
Chris@17
|
12 * @author Greg Sherwood <gsherwood@squiz.net>
|
Chris@17
|
13 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
|
Chris@17
|
14 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
Chris@17
|
15 */
|
Chris@17
|
16
|
Chris@17
|
17 namespace PHP_CodeSniffer;
|
Chris@17
|
18
|
Chris@17
|
19 if (class_exists('PHP_CodeSniffer\Autoload', false) === false) {
|
Chris@17
|
20 class Autoload
|
Chris@17
|
21 {
|
Chris@17
|
22
|
Chris@17
|
23 /**
|
Chris@17
|
24 * The composer autoloader.
|
Chris@17
|
25 *
|
Chris@18
|
26 * @var \Composer\Autoload\ClassLoader
|
Chris@17
|
27 */
|
Chris@17
|
28 private static $composerAutoloader = null;
|
Chris@17
|
29
|
Chris@17
|
30 /**
|
Chris@17
|
31 * A mapping of file names to class names.
|
Chris@17
|
32 *
|
Chris@17
|
33 * @var array<string, string>
|
Chris@17
|
34 */
|
Chris@17
|
35 private static $loadedClasses = [];
|
Chris@17
|
36
|
Chris@17
|
37 /**
|
Chris@17
|
38 * A mapping of class names to file names.
|
Chris@17
|
39 *
|
Chris@17
|
40 * @var array<string, string>
|
Chris@17
|
41 */
|
Chris@17
|
42 private static $loadedFiles = [];
|
Chris@17
|
43
|
Chris@17
|
44 /**
|
Chris@17
|
45 * A list of additional directories to search during autoloading.
|
Chris@17
|
46 *
|
Chris@17
|
47 * This is typically a list of coding standard directories.
|
Chris@17
|
48 *
|
Chris@17
|
49 * @var string[]
|
Chris@17
|
50 */
|
Chris@17
|
51 private static $searchPaths = [];
|
Chris@17
|
52
|
Chris@17
|
53
|
Chris@17
|
54 /**
|
Chris@17
|
55 * Loads a class.
|
Chris@17
|
56 *
|
Chris@17
|
57 * This method only loads classes that exist in the PHP_CodeSniffer namespace.
|
Chris@17
|
58 * All other classes are ignored and loaded by subsequent autoloaders.
|
Chris@17
|
59 *
|
Chris@17
|
60 * @param string $class The name of the class to load.
|
Chris@17
|
61 *
|
Chris@17
|
62 * @return bool
|
Chris@17
|
63 */
|
Chris@17
|
64 public static function load($class)
|
Chris@17
|
65 {
|
Chris@17
|
66 // Include the composer autoloader if there is one, but re-register it
|
Chris@17
|
67 // so this autoloader runs before the composer one as we need to include
|
Chris@17
|
68 // all files so we can figure out what the class/interface/trait name is.
|
Chris@17
|
69 if (self::$composerAutoloader === null) {
|
Chris@17
|
70 // Make sure we don't try to load any of Composer's classes
|
Chris@17
|
71 // while the autoloader is being setup.
|
Chris@17
|
72 if (strpos($class, 'Composer\\') === 0) {
|
Chris@17
|
73 return;
|
Chris@17
|
74 }
|
Chris@17
|
75
|
Chris@17
|
76 if (strpos(__DIR__, 'phar://') !== 0
|
Chris@17
|
77 && file_exists(__DIR__.'/../../autoload.php') === true
|
Chris@17
|
78 ) {
|
Chris@17
|
79 self::$composerAutoloader = include __DIR__.'/../../autoload.php';
|
Chris@17
|
80 if (self::$composerAutoloader instanceof \Composer\Autoload\ClassLoader) {
|
Chris@17
|
81 self::$composerAutoloader->unregister();
|
Chris@17
|
82 self::$composerAutoloader->register();
|
Chris@17
|
83 } else {
|
Chris@17
|
84 // Something went wrong, so keep going without the autoloader
|
Chris@17
|
85 // although namespaced sniffs might error.
|
Chris@17
|
86 self::$composerAutoloader = false;
|
Chris@17
|
87 }
|
Chris@17
|
88 } else {
|
Chris@17
|
89 self::$composerAutoloader = false;
|
Chris@17
|
90 }
|
Chris@17
|
91 }//end if
|
Chris@17
|
92
|
Chris@17
|
93 $ds = DIRECTORY_SEPARATOR;
|
Chris@17
|
94 $path = false;
|
Chris@17
|
95
|
Chris@17
|
96 if (substr($class, 0, 16) === 'PHP_CodeSniffer\\') {
|
Chris@17
|
97 if (substr($class, 0, 22) === 'PHP_CodeSniffer\Tests\\') {
|
Chris@17
|
98 $isInstalled = !is_dir(__DIR__.$ds.'tests');
|
Chris@17
|
99 if ($isInstalled === false) {
|
Chris@17
|
100 $path = __DIR__.$ds.'tests';
|
Chris@17
|
101 } else {
|
Chris@17
|
102 $path = '@test_dir@'.$ds.'PHP_CodeSniffer'.$ds.'CodeSniffer';
|
Chris@17
|
103 }
|
Chris@17
|
104
|
Chris@17
|
105 $path .= $ds.substr(str_replace('\\', $ds, $class), 22).'.php';
|
Chris@17
|
106 } else {
|
Chris@17
|
107 $path = __DIR__.$ds.'src'.$ds.substr(str_replace('\\', $ds, $class), 16).'.php';
|
Chris@17
|
108 }
|
Chris@17
|
109 }
|
Chris@17
|
110
|
Chris@17
|
111 // See if the composer autoloader knows where the class is.
|
Chris@17
|
112 if ($path === false && self::$composerAutoloader !== false) {
|
Chris@17
|
113 $path = self::$composerAutoloader->findFile($class);
|
Chris@17
|
114 }
|
Chris@17
|
115
|
Chris@17
|
116 // See if the class is inside one of our alternate search paths.
|
Chris@17
|
117 if ($path === false) {
|
Chris@17
|
118 foreach (self::$searchPaths as $searchPath => $nsPrefix) {
|
Chris@17
|
119 $className = $class;
|
Chris@17
|
120 if ($nsPrefix !== '' && substr($class, 0, strlen($nsPrefix)) === $nsPrefix) {
|
Chris@17
|
121 $className = substr($class, (strlen($nsPrefix) + 1));
|
Chris@17
|
122 }
|
Chris@17
|
123
|
Chris@17
|
124 $path = $searchPath.$ds.str_replace('\\', $ds, $className).'.php';
|
Chris@17
|
125 if (is_file($path) === true) {
|
Chris@17
|
126 break;
|
Chris@17
|
127 }
|
Chris@17
|
128
|
Chris@17
|
129 $path = false;
|
Chris@17
|
130 }
|
Chris@17
|
131 }
|
Chris@17
|
132
|
Chris@17
|
133 if ($path !== false && is_file($path) === true) {
|
Chris@17
|
134 self::loadFile($path);
|
Chris@17
|
135 return true;
|
Chris@17
|
136 }
|
Chris@17
|
137
|
Chris@17
|
138 return false;
|
Chris@17
|
139
|
Chris@17
|
140 }//end load()
|
Chris@17
|
141
|
Chris@17
|
142
|
Chris@17
|
143 /**
|
Chris@17
|
144 * Includes a file and tracks what class or interface was loaded as a result.
|
Chris@17
|
145 *
|
Chris@17
|
146 * @param string $path The path of the file to load.
|
Chris@17
|
147 *
|
Chris@17
|
148 * @return string The fully qualified name of the class in the loaded file.
|
Chris@17
|
149 */
|
Chris@17
|
150 public static function loadFile($path)
|
Chris@17
|
151 {
|
Chris@17
|
152 if (strpos(__DIR__, 'phar://') !== 0) {
|
Chris@17
|
153 $path = realpath($path);
|
Chris@17
|
154 if ($path === false) {
|
Chris@17
|
155 return false;
|
Chris@17
|
156 }
|
Chris@17
|
157 }
|
Chris@17
|
158
|
Chris@17
|
159 if (isset(self::$loadedClasses[$path]) === true) {
|
Chris@17
|
160 return self::$loadedClasses[$path];
|
Chris@17
|
161 }
|
Chris@17
|
162
|
Chris@17
|
163 $classes = get_declared_classes();
|
Chris@17
|
164 $interfaces = get_declared_interfaces();
|
Chris@17
|
165 $traits = get_declared_traits();
|
Chris@17
|
166
|
Chris@17
|
167 include $path;
|
Chris@17
|
168
|
Chris@17
|
169 $className = null;
|
Chris@17
|
170 $newClasses = array_reverse(array_diff(get_declared_classes(), $classes));
|
Chris@17
|
171 foreach ($newClasses as $name) {
|
Chris@17
|
172 if (isset(self::$loadedFiles[$name]) === false) {
|
Chris@17
|
173 $className = $name;
|
Chris@17
|
174 break;
|
Chris@17
|
175 }
|
Chris@17
|
176 }
|
Chris@17
|
177
|
Chris@17
|
178 if ($className === null) {
|
Chris@17
|
179 $newClasses = array_reverse(array_diff(get_declared_interfaces(), $interfaces));
|
Chris@17
|
180 foreach ($newClasses as $name) {
|
Chris@17
|
181 if (isset(self::$loadedFiles[$name]) === false) {
|
Chris@17
|
182 $className = $name;
|
Chris@17
|
183 break;
|
Chris@17
|
184 }
|
Chris@17
|
185 }
|
Chris@17
|
186 }
|
Chris@17
|
187
|
Chris@17
|
188 if ($className === null) {
|
Chris@17
|
189 $newClasses = array_reverse(array_diff(get_declared_traits(), $traits));
|
Chris@17
|
190 foreach ($newClasses as $name) {
|
Chris@17
|
191 if (isset(self::$loadedFiles[$name]) === false) {
|
Chris@17
|
192 $className = $name;
|
Chris@17
|
193 break;
|
Chris@17
|
194 }
|
Chris@17
|
195 }
|
Chris@17
|
196 }
|
Chris@17
|
197
|
Chris@17
|
198 self::$loadedClasses[$path] = $className;
|
Chris@17
|
199 self::$loadedFiles[$className] = $path;
|
Chris@17
|
200 return self::$loadedClasses[$path];
|
Chris@17
|
201
|
Chris@17
|
202 }//end loadFile()
|
Chris@17
|
203
|
Chris@17
|
204
|
Chris@17
|
205 /**
|
Chris@17
|
206 * Adds a directory to search during autoloading.
|
Chris@17
|
207 *
|
Chris@17
|
208 * @param string $path The path to the directory to search.
|
Chris@17
|
209 * @param string $nsPrefix The namespace prefix used by files under this path.
|
Chris@17
|
210 *
|
Chris@17
|
211 * @return void
|
Chris@17
|
212 */
|
Chris@17
|
213 public static function addSearchPath($path, $nsPrefix='')
|
Chris@17
|
214 {
|
Chris@17
|
215 self::$searchPaths[$path] = rtrim(trim((string) $nsPrefix), '\\');
|
Chris@17
|
216
|
Chris@17
|
217 }//end addSearchPath()
|
Chris@17
|
218
|
Chris@17
|
219
|
Chris@17
|
220 /**
|
Chris@17
|
221 * Retrieve the namespaces and paths registered by external standards.
|
Chris@17
|
222 *
|
Chris@17
|
223 * @return array
|
Chris@17
|
224 */
|
Chris@17
|
225 public static function getSearchPaths()
|
Chris@17
|
226 {
|
Chris@17
|
227 return self::$searchPaths;
|
Chris@17
|
228
|
Chris@17
|
229 }//end getSearchPaths()
|
Chris@17
|
230
|
Chris@17
|
231
|
Chris@17
|
232 /**
|
Chris@17
|
233 * Gets the class name for the given file path.
|
Chris@17
|
234 *
|
Chris@17
|
235 * @param string $path The name of the file.
|
Chris@17
|
236 *
|
Chris@17
|
237 * @throws \Exception If the file path has not been loaded.
|
Chris@17
|
238 * @return string
|
Chris@17
|
239 */
|
Chris@17
|
240 public static function getLoadedClassName($path)
|
Chris@17
|
241 {
|
Chris@17
|
242 if (isset(self::$loadedClasses[$path]) === false) {
|
Chris@17
|
243 throw new \Exception("Cannot get class name for $path; file has not been included");
|
Chris@17
|
244 }
|
Chris@17
|
245
|
Chris@17
|
246 return self::$loadedClasses[$path];
|
Chris@17
|
247
|
Chris@17
|
248 }//end getLoadedClassName()
|
Chris@17
|
249
|
Chris@17
|
250
|
Chris@17
|
251 /**
|
Chris@17
|
252 * Gets the file path for the given class name.
|
Chris@17
|
253 *
|
Chris@17
|
254 * @param string $class The name of the class.
|
Chris@17
|
255 *
|
Chris@17
|
256 * @throws \Exception If the class name has not been loaded
|
Chris@17
|
257 * @return string
|
Chris@17
|
258 */
|
Chris@17
|
259 public static function getLoadedFileName($class)
|
Chris@17
|
260 {
|
Chris@17
|
261 if (isset(self::$loadedFiles[$class]) === false) {
|
Chris@17
|
262 throw new \Exception("Cannot get file name for $class; class has not been included");
|
Chris@17
|
263 }
|
Chris@17
|
264
|
Chris@17
|
265 return self::$loadedFiles[$class];
|
Chris@17
|
266
|
Chris@17
|
267 }//end getLoadedFileName()
|
Chris@17
|
268
|
Chris@17
|
269
|
Chris@17
|
270 /**
|
Chris@17
|
271 * Gets the mapping of file names to class names.
|
Chris@17
|
272 *
|
Chris@17
|
273 * @return array<string, string>
|
Chris@17
|
274 */
|
Chris@17
|
275 public static function getLoadedClasses()
|
Chris@17
|
276 {
|
Chris@17
|
277 return self::$loadedClasses;
|
Chris@17
|
278
|
Chris@17
|
279 }//end getLoadedClasses()
|
Chris@17
|
280
|
Chris@17
|
281
|
Chris@17
|
282 /**
|
Chris@17
|
283 * Gets the mapping of class names to file names.
|
Chris@17
|
284 *
|
Chris@17
|
285 * @return array<string, string>
|
Chris@17
|
286 */
|
Chris@17
|
287 public static function getLoadedFiles()
|
Chris@17
|
288 {
|
Chris@17
|
289 return self::$loadedFiles;
|
Chris@17
|
290
|
Chris@17
|
291 }//end getLoadedFiles()
|
Chris@17
|
292
|
Chris@17
|
293
|
Chris@17
|
294 }//end class
|
Chris@17
|
295
|
Chris@17
|
296 // Register the autoloader before any existing autoloaders to ensure
|
Chris@17
|
297 // it gets a chance to hear about every autoload request, and record
|
Chris@17
|
298 // the file and class name for it.
|
Chris@17
|
299 spl_autoload_register(__NAMESPACE__.'\Autoload::load', true, true);
|
Chris@17
|
300 }//end if
|