1 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,525 +0,0 @@ |
1 |
-<?php |
|
2 |
- |
|
3 |
-/* |
|
4 |
- * This file is part of 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 |
- |
|
12 |
-namespace Symfony\Component\Debug; |
|
13 |
- |
|
14 |
-use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation; |
|
15 |
- |
|
16 |
-/** |
|
17 |
- * Autoloader checking if the class is really defined in the file found. |
|
18 |
- * |
|
19 |
- * The ClassLoader will wrap all registered autoloaders |
|
20 |
- * and will throw an exception if a file is found but does |
|
21 |
- * not declare the class. |
|
22 |
- * |
|
23 |
- * @author Fabien Potencier <fabien@symfony.com> |
|
24 |
- * @author Christophe Coevoet <stof@notk.org> |
|
25 |
- * @author Nicolas Grekas <p@tchwork.com> |
|
26 |
- * @author Guilhem Niot <guilhem.niot@gmail.com> |
|
27 |
- */ |
|
28 |
-class DebugClassLoader |
|
29 |
-{ |
|
30 |
- private $classLoader; |
|
31 |
- private $isFinder; |
|
32 |
- private $loaded = []; |
|
33 |
- private static $caseCheck; |
|
34 |
- private static $checkedClasses = []; |
|
35 |
- private static $final = []; |
|
36 |
- private static $finalMethods = []; |
|
37 |
- private static $deprecated = []; |
|
38 |
- private static $internal = []; |
|
39 |
- private static $internalMethods = []; |
|
40 |
- private static $annotatedParameters = []; |
|
41 |
- private static $darwinCache = ['/' => ['/', []]]; |
|
42 |
- private static $method = []; |
|
43 |
- |
|
44 |
- public function __construct(callable $classLoader) |
|
45 |
- { |
|
46 |
- $this->classLoader = $classLoader; |
|
47 |
- $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile'); |
|
48 |
- |
|
49 |
- if (!isset(self::$caseCheck)) { |
|
50 |
- $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR); |
|
51 |
- $i = strrpos($file, \DIRECTORY_SEPARATOR); |
|
52 |
- $dir = substr($file, 0, 1 + $i); |
|
53 |
- $file = substr($file, 1 + $i); |
|
54 |
- $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); |
|
55 |
- $test = realpath($dir.$test); |
|
56 |
- |
|
57 |
- if (false === $test || false === $i) { |
|
58 |
- // filesystem is case sensitive |
|
59 |
- self::$caseCheck = 0; |
|
60 |
- } elseif (substr($test, -\strlen($file)) === $file) { |
|
61 |
- // filesystem is case insensitive and realpath() normalizes the case of characters |
|
62 |
- self::$caseCheck = 1; |
|
63 |
- } elseif (false !== stripos(PHP_OS, 'darwin')) { |
|
64 |
- // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters |
|
65 |
- self::$caseCheck = 2; |
|
66 |
- } else { |
|
67 |
- // filesystem case checks failed, fallback to disabling them |
|
68 |
- self::$caseCheck = 0; |
|
69 |
- } |
|
70 |
- } |
|
71 |
- } |
|
72 |
- |
|
73 |
- /** |
|
74 |
- * Gets the wrapped class loader. |
|
75 |
- * |
|
76 |
- * @return callable The wrapped class loader |
|
77 |
- */ |
|
78 |
- public function getClassLoader() |
|
79 |
- { |
|
80 |
- return $this->classLoader; |
|
81 |
- } |
|
82 |
- |
|
83 |
- /** |
|
84 |
- * Wraps all autoloaders. |
|
85 |
- */ |
|
86 |
- public static function enable() |
|
87 |
- { |
|
88 |
- // Ensures we don't hit https://bugs.php.net/42098 |
|
89 |
- class_exists('Symfony\Component\Debug\ErrorHandler'); |
|
90 |
- class_exists('Psr\Log\LogLevel'); |
|
91 |
- |
|
92 |
- if (!\is_array($functions = spl_autoload_functions())) { |
|
93 |
- return; |
|
94 |
- } |
|
95 |
- |
|
96 |
- foreach ($functions as $function) { |
|
97 |
- spl_autoload_unregister($function); |
|
98 |
- } |
|
99 |
- |
|
100 |
- foreach ($functions as $function) { |
|
101 |
- if (!\is_array($function) || !$function[0] instanceof self) { |
|
102 |
- $function = [new static($function), 'loadClass']; |
|
103 |
- } |
|
104 |
- |
|
105 |
- spl_autoload_register($function); |
|
106 |
- } |
|
107 |
- } |
|
108 |
- |
|
109 |
- /** |
|
110 |
- * Disables the wrapping. |
|
111 |
- */ |
|
112 |
- public static function disable() |
|
113 |
- { |
|
114 |
- if (!\is_array($functions = spl_autoload_functions())) { |
|
115 |
- return; |
|
116 |
- } |
|
117 |
- |
|
118 |
- foreach ($functions as $function) { |
|
119 |
- spl_autoload_unregister($function); |
|
120 |
- } |
|
121 |
- |
|
122 |
- foreach ($functions as $function) { |
|
123 |
- if (\is_array($function) && $function[0] instanceof self) { |
|
124 |
- $function = $function[0]->getClassLoader(); |
|
125 |
- } |
|
126 |
- |
|
127 |
- spl_autoload_register($function); |
|
128 |
- } |
|
129 |
- } |
|
130 |
- |
|
131 |
- /** |
|
132 |
- * @return string|null |
|
133 |
- */ |
|
134 |
- public function findFile($class) |
|
135 |
- { |
|
136 |
- return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null; |
|
137 |
- } |
|
138 |
- |
|
139 |
- /** |
|
140 |
- * Loads the given class or interface. |
|
141 |
- * |
|
142 |
- * @param string $class The name of the class |
|
143 |
- * |
|
144 |
- * @throws \RuntimeException |
|
145 |
- */ |
|
146 |
- public function loadClass($class) |
|
147 |
- { |
|
148 |
- $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); |
|
149 |
- |
|
150 |
- try { |
|
151 |
- if ($this->isFinder && !isset($this->loaded[$class])) { |
|
152 |
- $this->loaded[$class] = true; |
|
153 |
- if (!$file = $this->classLoader[0]->findFile($class) ?: false) { |
|
154 |
- // no-op |
|
155 |
- } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) { |
|
156 |
- include $file; |
|
157 |
- |
|
158 |
- return; |
|
159 |
- } elseif (false === include $file) { |
|
160 |
- return; |
|
161 |
- } |
|
162 |
- } else { |
|
163 |
- ($this->classLoader)($class); |
|
164 |
- $file = false; |
|
165 |
- } |
|
166 |
- } finally { |
|
167 |
- error_reporting($e); |
|
168 |
- } |
|
169 |
- |
|
170 |
- $this->checkClass($class, $file); |
|
171 |
- } |
|
172 |
- |
|
173 |
- private function checkClass($class, $file = null) |
|
174 |
- { |
|
175 |
- $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); |
|
176 |
- |
|
177 |
- if (null !== $file && $class && '\\' === $class[0]) { |
|
178 |
- $class = substr($class, 1); |
|
179 |
- } |
|
180 |
- |
|
181 |
- if ($exists) { |
|
182 |
- if (isset(self::$checkedClasses[$class])) { |
|
183 |
- return; |
|
184 |
- } |
|
185 |
- self::$checkedClasses[$class] = true; |
|
186 |
- |
|
187 |
- $refl = new \ReflectionClass($class); |
|
188 |
- if (null === $file && $refl->isInternal()) { |
|
189 |
- return; |
|
190 |
- } |
|
191 |
- $name = $refl->getName(); |
|
192 |
- |
|
193 |
- if ($name !== $class && 0 === strcasecmp($name, $class)) { |
|
194 |
- throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); |
|
195 |
- } |
|
196 |
- |
|
197 |
- $deprecations = $this->checkAnnotations($refl, $name); |
|
198 |
- |
|
199 |
- foreach ($deprecations as $message) { |
|
200 |
- @trigger_error($message, E_USER_DEPRECATED); |
|
201 |
- } |
|
202 |
- } |
|
203 |
- |
|
204 |
- if (!$file) { |
|
205 |
- return; |
|
206 |
- } |
|
207 |
- |
|
208 |
- if (!$exists) { |
|
209 |
- if (false !== strpos($class, '/')) { |
|
210 |
- throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); |
|
211 |
- } |
|
212 |
- |
|
213 |
- throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); |
|
214 |
- } |
|
215 |
- |
|
216 |
- if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) { |
|
217 |
- throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2])); |
|
218 |
- } |
|
219 |
- } |
|
220 |
- |
|
221 |
- public function checkAnnotations(\ReflectionClass $refl, $class) |
|
222 |
- { |
|
223 |
- $deprecations = []; |
|
224 |
- |
|
225 |
- // Don't trigger deprecations for classes in the same vendor |
|
226 |
- if (2 > $len = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) { |
|
227 |
- $len = 0; |
|
228 |
- $ns = ''; |
|
229 |
- } else { |
|
230 |
- $ns = str_replace('_', '\\', substr($class, 0, $len)); |
|
231 |
- } |
|
232 |
- |
|
233 |
- // Detect annotations on the class |
|
234 |
- if (false !== $doc = $refl->getDocComment()) { |
|
235 |
- foreach (['final', 'deprecated', 'internal'] as $annotation) { |
|
236 |
- if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) { |
|
237 |
- self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : ''; |
|
238 |
- } |
|
239 |
- } |
|
240 |
- |
|
241 |
- if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+(?:[\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) { |
|
242 |
- foreach ($notice as $method) { |
|
243 |
- $static = '' !== $method[1]; |
|
244 |
- $name = $method[2]; |
|
245 |
- $description = $method[3] ?? null; |
|
246 |
- if (false === strpos($name, '(')) { |
|
247 |
- $name .= '()'; |
|
248 |
- } |
|
249 |
- if (null !== $description) { |
|
250 |
- $description = trim($description); |
|
251 |
- if (!isset($method[4])) { |
|
252 |
- $description .= '.'; |
|
253 |
- } |
|
254 |
- } |
|
255 |
- self::$method[$class][] = [$class, $name, $static, $description]; |
|
256 |
- } |
|
257 |
- } |
|
258 |
- } |
|
259 |
- |
|
260 |
- $parent = get_parent_class($class); |
|
261 |
- $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent); |
|
262 |
- if ($parent) { |
|
263 |
- $parentAndOwnInterfaces[$parent] = $parent; |
|
264 |
- |
|
265 |
- if (!isset(self::$checkedClasses[$parent])) { |
|
266 |
- $this->checkClass($parent); |
|
267 |
- } |
|
268 |
- |
|
269 |
- if (isset(self::$final[$parent])) { |
|
270 |
- $deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $class); |
|
271 |
- } |
|
272 |
- } |
|
273 |
- |
|
274 |
- // Detect if the parent is annotated |
|
275 |
- foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) { |
|
276 |
- if (!isset(self::$checkedClasses[$use])) { |
|
277 |
- $this->checkClass($use); |
|
278 |
- } |
|
279 |
- if (isset(self::$deprecated[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) { |
|
280 |
- $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait'); |
|
281 |
- $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses'); |
|
282 |
- |
|
283 |
- $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]); |
|
284 |
- } |
|
285 |
- if (isset(self::$internal[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len)) { |
|
286 |
- $deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class); |
|
287 |
- } |
|
288 |
- if (isset(self::$method[$use])) { |
|
289 |
- if ($refl->isAbstract()) { |
|
290 |
- if (isset(self::$method[$class])) { |
|
291 |
- self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]); |
|
292 |
- } else { |
|
293 |
- self::$method[$class] = self::$method[$use]; |
|
294 |
- } |
|
295 |
- } elseif (!$refl->isInterface()) { |
|
296 |
- $hasCall = $refl->hasMethod('__call'); |
|
297 |
- $hasStaticCall = $refl->hasMethod('__callStatic'); |
|
298 |
- foreach (self::$method[$use] as $method) { |
|
299 |
- list($interface, $name, $static, $description) = $method; |
|
300 |
- if ($static ? $hasStaticCall : $hasCall) { |
|
301 |
- continue; |
|
302 |
- } |
|
303 |
- $realName = substr($name, 0, strpos($name, '(')); |
|
304 |
- if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) { |
|
305 |
- $deprecations[] = sprintf('Class "%s" should implement method "%s::%s"%s', $class, ($static ? 'static ' : '').$interface, $name, null == $description ? '.' : ': '.$description); |
|
306 |
- } |
|
307 |
- } |
|
308 |
- } |
|
309 |
- } |
|
310 |
- } |
|
311 |
- |
|
312 |
- if (trait_exists($class)) { |
|
313 |
- return $deprecations; |
|
314 |
- } |
|
315 |
- |
|
316 |
- // Inherit @final, @internal and @param annotations for methods |
|
317 |
- self::$finalMethods[$class] = []; |
|
318 |
- self::$internalMethods[$class] = []; |
|
319 |
- self::$annotatedParameters[$class] = []; |
|
320 |
- foreach ($parentAndOwnInterfaces as $use) { |
|
321 |
- foreach (['finalMethods', 'internalMethods', 'annotatedParameters'] as $property) { |
|
322 |
- if (isset(self::${$property}[$use])) { |
|
323 |
- self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use]; |
|
324 |
- } |
|
325 |
- } |
|
326 |
- } |
|
327 |
- |
|
328 |
- foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { |
|
329 |
- if ($method->class !== $class) { |
|
330 |
- continue; |
|
331 |
- } |
|
332 |
- |
|
333 |
- if ($parent && isset(self::$finalMethods[$parent][$method->name])) { |
|
334 |
- list($declaringClass, $message) = self::$finalMethods[$parent][$method->name]; |
|
335 |
- $deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class); |
|
336 |
- } |
|
337 |
- |
|
338 |
- if (isset(self::$internalMethods[$class][$method->name])) { |
|
339 |
- list($declaringClass, $message) = self::$internalMethods[$class][$method->name]; |
|
340 |
- if (strncmp($ns, $declaringClass, $len)) { |
|
341 |
- $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class); |
|
342 |
- } |
|
343 |
- } |
|
344 |
- |
|
345 |
- // To read method annotations |
|
346 |
- $doc = $method->getDocComment(); |
|
347 |
- |
|
348 |
- if (isset(self::$annotatedParameters[$class][$method->name])) { |
|
349 |
- $definedParameters = []; |
|
350 |
- foreach ($method->getParameters() as $parameter) { |
|
351 |
- $definedParameters[$parameter->name] = true; |
|
352 |
- } |
|
353 |
- |
|
354 |
- foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) { |
|
355 |
- if (!isset($definedParameters[$parameterName]) && !($doc && preg_match("/\\n\\s+\\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\\\${$parameterName}\\b/", $doc))) { |
|
356 |
- $deprecations[] = sprintf($deprecation, $class); |
|
357 |
- } |
|
358 |
- } |
|
359 |
- } |
|
360 |
- |
|
361 |
- if (!$doc) { |
|
362 |
- continue; |
|
363 |
- } |
|
364 |
- |
|
365 |
- $finalOrInternal = false; |
|
366 |
- |
|
367 |
- foreach (['final', 'internal'] as $annotation) { |
|
368 |
- if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) { |
|
369 |
- $message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : ''; |
|
370 |
- self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message]; |
|
371 |
- $finalOrInternal = true; |
|
372 |
- } |
|
373 |
- } |
|
374 |
- |
|
375 |
- if ($finalOrInternal || $method->isConstructor() || false === strpos($doc, '@param') || StatelessInvocation::class === $class) { |
|
376 |
- continue; |
|
377 |
- } |
|
378 |
- if (!preg_match_all('#\n\s+\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, PREG_SET_ORDER)) { |
|
379 |
- continue; |
|
380 |
- } |
|
381 |
- if (!isset(self::$annotatedParameters[$class][$method->name])) { |
|
382 |
- $definedParameters = []; |
|
383 |
- foreach ($method->getParameters() as $parameter) { |
|
384 |
- $definedParameters[$parameter->name] = true; |
|
385 |
- } |
|
386 |
- } |
|
387 |
- foreach ($matches as list(, $parameterType, $parameterName)) { |
|
388 |
- if (!isset($definedParameters[$parameterName])) { |
|
389 |
- $parameterType = trim($parameterType); |
|
390 |
- self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $method->class); |
|
391 |
- } |
|
392 |
- } |
|
393 |
- } |
|
394 |
- |
|
395 |
- return $deprecations; |
|
396 |
- } |
|
397 |
- |
|
398 |
- public function checkCase(\ReflectionClass $refl, $file, $class) |
|
399 |
- { |
|
400 |
- $real = explode('\\', $class.strrchr($file, '.')); |
|
401 |
- $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file)); |
|
402 |
- |
|
403 |
- $i = \count($tail) - 1; |
|
404 |
- $j = \count($real) - 1; |
|
405 |
- |
|
406 |
- while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { |
|
407 |
- --$i; |
|
408 |
- --$j; |
|
409 |
- } |
|
410 |
- |
|
411 |
- array_splice($tail, 0, $i + 1); |
|
412 |
- |
|
413 |
- if (!$tail) { |
|
414 |
- return; |
|
415 |
- } |
|
416 |
- |
|
417 |
- $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail); |
|
418 |
- $tailLen = \strlen($tail); |
|
419 |
- $real = $refl->getFileName(); |
|
420 |
- |
|
421 |
- if (2 === self::$caseCheck) { |
|
422 |
- $real = $this->darwinRealpath($real); |
|
423 |
- } |
|
424 |
- |
|
425 |
- if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) |
|
426 |
- && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) |
|
427 |
- ) { |
|
428 |
- return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)]; |
|
429 |
- } |
|
430 |
- } |
|
431 |
- |
|
432 |
- /** |
|
433 |
- * `realpath` on MacOSX doesn't normalize the case of characters. |
|
434 |
- */ |
|
435 |
- private function darwinRealpath($real) |
|
436 |
- { |
|
437 |
- $i = 1 + strrpos($real, '/'); |
|
438 |
- $file = substr($real, $i); |
|
439 |
- $real = substr($real, 0, $i); |
|
440 |
- |
|
441 |
- if (isset(self::$darwinCache[$real])) { |
|
442 |
- $kDir = $real; |
|
443 |
- } else { |
|
444 |
- $kDir = strtolower($real); |
|
445 |
- |
|
446 |
- if (isset(self::$darwinCache[$kDir])) { |
|
447 |
- $real = self::$darwinCache[$kDir][0]; |
|
448 |
- } else { |
|
449 |
- $dir = getcwd(); |
|
450 |
- chdir($real); |
|
451 |
- $real = getcwd().'/'; |
|
452 |
- chdir($dir); |
|
453 |
- |
|
454 |
- $dir = $real; |
|
455 |
- $k = $kDir; |
|
456 |
- $i = \strlen($dir) - 1; |
|
457 |
- while (!isset(self::$darwinCache[$k])) { |
|
458 |
- self::$darwinCache[$k] = [$dir, []]; |
|
459 |
- self::$darwinCache[$dir] = &self::$darwinCache[$k]; |
|
460 |
- |
|
461 |
- while ('/' !== $dir[--$i]) { |
|
462 |
- } |
|
463 |
- $k = substr($k, 0, ++$i); |
|
464 |
- $dir = substr($dir, 0, $i--); |
|
465 |
- } |
|
466 |
- } |
|
467 |
- } |
|
468 |
- |
|
469 |
- $dirFiles = self::$darwinCache[$kDir][1]; |
|
470 |
- |
|
471 |
- if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) { |
|
472 |
- // Get the file name from "file_name.php(123) : eval()'d code" |
|
473 |
- $file = substr($file, 0, strrpos($file, '(', -17)); |
|
474 |
- } |
|
475 |
- |
|
476 |
- if (isset($dirFiles[$file])) { |
|
477 |
- return $real .= $dirFiles[$file]; |
|
478 |
- } |
|
479 |
- |
|
480 |
- $kFile = strtolower($file); |
|
481 |
- |
|
482 |
- if (!isset($dirFiles[$kFile])) { |
|
483 |
- foreach (scandir($real, 2) as $f) { |
|
484 |
- if ('.' !== $f[0]) { |
|
485 |
- $dirFiles[$f] = $f; |
|
486 |
- if ($f === $file) { |
|
487 |
- $kFile = $k = $file; |
|
488 |
- } elseif ($f !== $k = strtolower($f)) { |
|
489 |
- $dirFiles[$k] = $f; |
|
490 |
- } |
|
491 |
- } |
|
492 |
- } |
|
493 |
- self::$darwinCache[$kDir][1] = $dirFiles; |
|
494 |
- } |
|
495 |
- |
|
496 |
- return $real .= $dirFiles[$kFile]; |
|
497 |
- } |
|
498 |
- |
|
499 |
- /** |
|
500 |
- * `class_implements` includes interfaces from the parents so we have to manually exclude them. |
|
501 |
- * |
|
502 |
- * @param string $class |
|
503 |
- * @param string|false $parent |
|
504 |
- * |
|
505 |
- * @return string[] |
|
506 |
- */ |
|
507 |
- private function getOwnInterfaces($class, $parent) |
|
508 |
- { |
|
509 |
- $ownInterfaces = class_implements($class, false); |
|
510 |
- |
|
511 |
- if ($parent) { |
|
512 |
- foreach (class_implements($parent, false) as $interface) { |
|
513 |
- unset($ownInterfaces[$interface]); |
|
514 |
- } |
|
515 |
- } |
|
516 |
- |
|
517 |
- foreach ($ownInterfaces as $interface) { |
|
518 |
- foreach (class_implements($interface) as $interface) { |
|
519 |
- unset($ownInterfaces[$interface]); |
|
520 |
- } |
|
521 |
- } |
|
522 |
- |
|
523 |
- return $ownInterfaces; |
|
524 |
- } |
|
525 |
-} |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,525 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of 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 |
+ |
|
12 |
+namespace Symfony\Component\Debug; |
|
13 |
+ |
|
14 |
+use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation; |
|
15 |
+ |
|
16 |
+/** |
|
17 |
+ * Autoloader checking if the class is really defined in the file found. |
|
18 |
+ * |
|
19 |
+ * The ClassLoader will wrap all registered autoloaders |
|
20 |
+ * and will throw an exception if a file is found but does |
|
21 |
+ * not declare the class. |
|
22 |
+ * |
|
23 |
+ * @author Fabien Potencier <fabien@symfony.com> |
|
24 |
+ * @author Christophe Coevoet <stof@notk.org> |
|
25 |
+ * @author Nicolas Grekas <p@tchwork.com> |
|
26 |
+ * @author Guilhem Niot <guilhem.niot@gmail.com> |
|
27 |
+ */ |
|
28 |
+class DebugClassLoader |
|
29 |
+{ |
|
30 |
+ private $classLoader; |
|
31 |
+ private $isFinder; |
|
32 |
+ private $loaded = []; |
|
33 |
+ private static $caseCheck; |
|
34 |
+ private static $checkedClasses = []; |
|
35 |
+ private static $final = []; |
|
36 |
+ private static $finalMethods = []; |
|
37 |
+ private static $deprecated = []; |
|
38 |
+ private static $internal = []; |
|
39 |
+ private static $internalMethods = []; |
|
40 |
+ private static $annotatedParameters = []; |
|
41 |
+ private static $darwinCache = ['/' => ['/', []]]; |
|
42 |
+ private static $method = []; |
|
43 |
+ |
|
44 |
+ public function __construct(callable $classLoader) |
|
45 |
+ { |
|
46 |
+ $this->classLoader = $classLoader; |
|
47 |
+ $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile'); |
|
48 |
+ |
|
49 |
+ if (!isset(self::$caseCheck)) { |
|
50 |
+ $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR); |
|
51 |
+ $i = strrpos($file, \DIRECTORY_SEPARATOR); |
|
52 |
+ $dir = substr($file, 0, 1 + $i); |
|
53 |
+ $file = substr($file, 1 + $i); |
|
54 |
+ $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); |
|
55 |
+ $test = realpath($dir.$test); |
|
56 |
+ |
|
57 |
+ if (false === $test || false === $i) { |
|
58 |
+ // filesystem is case sensitive |
|
59 |
+ self::$caseCheck = 0; |
|
60 |
+ } elseif (substr($test, -\strlen($file)) === $file) { |
|
61 |
+ // filesystem is case insensitive and realpath() normalizes the case of characters |
|
62 |
+ self::$caseCheck = 1; |
|
63 |
+ } elseif (false !== stripos(PHP_OS, 'darwin')) { |
|
64 |
+ // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters |
|
65 |
+ self::$caseCheck = 2; |
|
66 |
+ } else { |
|
67 |
+ // filesystem case checks failed, fallback to disabling them |
|
68 |
+ self::$caseCheck = 0; |
|
69 |
+ } |
|
70 |
+ } |
|
71 |
+ } |
|
72 |
+ |
|
73 |
+ /** |
|
74 |
+ * Gets the wrapped class loader. |
|
75 |
+ * |
|
76 |
+ * @return callable The wrapped class loader |
|
77 |
+ */ |
|
78 |
+ public function getClassLoader() |
|
79 |
+ { |
|
80 |
+ return $this->classLoader; |
|
81 |
+ } |
|
82 |
+ |
|
83 |
+ /** |
|
84 |
+ * Wraps all autoloaders. |
|
85 |
+ */ |
|
86 |
+ public static function enable() |
|
87 |
+ { |
|
88 |
+ // Ensures we don't hit https://bugs.php.net/42098 |
|
89 |
+ class_exists('Symfony\Component\Debug\ErrorHandler'); |
|
90 |
+ class_exists('Psr\Log\LogLevel'); |
|
91 |
+ |
|
92 |
+ if (!\is_array($functions = spl_autoload_functions())) { |
|
93 |
+ return; |
|
94 |
+ } |
|
95 |
+ |
|
96 |
+ foreach ($functions as $function) { |
|
97 |
+ spl_autoload_unregister($function); |
|
98 |
+ } |
|
99 |
+ |
|
100 |
+ foreach ($functions as $function) { |
|
101 |
+ if (!\is_array($function) || !$function[0] instanceof self) { |
|
102 |
+ $function = [new static($function), 'loadClass']; |
|
103 |
+ } |
|
104 |
+ |
|
105 |
+ spl_autoload_register($function); |
|
106 |
+ } |
|
107 |
+ } |
|
108 |
+ |
|
109 |
+ /** |
|
110 |
+ * Disables the wrapping. |
|
111 |
+ */ |
|
112 |
+ public static function disable() |
|
113 |
+ { |
|
114 |
+ if (!\is_array($functions = spl_autoload_functions())) { |
|
115 |
+ return; |
|
116 |
+ } |
|
117 |
+ |
|
118 |
+ foreach ($functions as $function) { |
|
119 |
+ spl_autoload_unregister($function); |
|
120 |
+ } |
|
121 |
+ |
|
122 |
+ foreach ($functions as $function) { |
|
123 |
+ if (\is_array($function) && $function[0] instanceof self) { |
|
124 |
+ $function = $function[0]->getClassLoader(); |
|
125 |
+ } |
|
126 |
+ |
|
127 |
+ spl_autoload_register($function); |
|
128 |
+ } |
|
129 |
+ } |
|
130 |
+ |
|
131 |
+ /** |
|
132 |
+ * @return string|null |
|
133 |
+ */ |
|
134 |
+ public function findFile($class) |
|
135 |
+ { |
|
136 |
+ return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null; |
|
137 |
+ } |
|
138 |
+ |
|
139 |
+ /** |
|
140 |
+ * Loads the given class or interface. |
|
141 |
+ * |
|
142 |
+ * @param string $class The name of the class |
|
143 |
+ * |
|
144 |
+ * @throws \RuntimeException |
|
145 |
+ */ |
|
146 |
+ public function loadClass($class) |
|
147 |
+ { |
|
148 |
+ $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); |
|
149 |
+ |
|
150 |
+ try { |
|
151 |
+ if ($this->isFinder && !isset($this->loaded[$class])) { |
|
152 |
+ $this->loaded[$class] = true; |
|
153 |
+ if (!$file = $this->classLoader[0]->findFile($class) ?: false) { |
|
154 |
+ // no-op |
|
155 |
+ } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) { |
|
156 |
+ include $file; |
|
157 |
+ |
|
158 |
+ return; |
|
159 |
+ } elseif (false === include $file) { |
|
160 |
+ return; |
|
161 |
+ } |
|
162 |
+ } else { |
|
163 |
+ ($this->classLoader)($class); |
|
164 |
+ $file = false; |
|
165 |
+ } |
|
166 |
+ } finally { |
|
167 |
+ error_reporting($e); |
|
168 |
+ } |
|
169 |
+ |
|
170 |
+ $this->checkClass($class, $file); |
|
171 |
+ } |
|
172 |
+ |
|
173 |
+ private function checkClass($class, $file = null) |
|
174 |
+ { |
|
175 |
+ $exists = null === $file || class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false); |
|
176 |
+ |
|
177 |
+ if (null !== $file && $class && '\\' === $class[0]) { |
|
178 |
+ $class = substr($class, 1); |
|
179 |
+ } |
|
180 |
+ |
|
181 |
+ if ($exists) { |
|
182 |
+ if (isset(self::$checkedClasses[$class])) { |
|
183 |
+ return; |
|
184 |
+ } |
|
185 |
+ self::$checkedClasses[$class] = true; |
|
186 |
+ |
|
187 |
+ $refl = new \ReflectionClass($class); |
|
188 |
+ if (null === $file && $refl->isInternal()) { |
|
189 |
+ return; |
|
190 |
+ } |
|
191 |
+ $name = $refl->getName(); |
|
192 |
+ |
|
193 |
+ if ($name !== $class && 0 === strcasecmp($name, $class)) { |
|
194 |
+ throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); |
|
195 |
+ } |
|
196 |
+ |
|
197 |
+ $deprecations = $this->checkAnnotations($refl, $name); |
|
198 |
+ |
|
199 |
+ foreach ($deprecations as $message) { |
|
200 |
+ @trigger_error($message, E_USER_DEPRECATED); |
|
201 |
+ } |
|
202 |
+ } |
|
203 |
+ |
|
204 |
+ if (!$file) { |
|
205 |
+ return; |
|
206 |
+ } |
|
207 |
+ |
|
208 |
+ if (!$exists) { |
|
209 |
+ if (false !== strpos($class, '/')) { |
|
210 |
+ throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); |
|
211 |
+ } |
|
212 |
+ |
|
213 |
+ throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); |
|
214 |
+ } |
|
215 |
+ |
|
216 |
+ if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) { |
|
217 |
+ throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2])); |
|
218 |
+ } |
|
219 |
+ } |
|
220 |
+ |
|
221 |
+ public function checkAnnotations(\ReflectionClass $refl, $class) |
|
222 |
+ { |
|
223 |
+ $deprecations = []; |
|
224 |
+ |
|
225 |
+ // Don't trigger deprecations for classes in the same vendor |
|
226 |
+ if (2 > $len = 1 + (strpos($class, '\\') ?: strpos($class, '_'))) { |
|
227 |
+ $len = 0; |
|
228 |
+ $ns = ''; |
|
229 |
+ } else { |
|
230 |
+ $ns = str_replace('_', '\\', substr($class, 0, $len)); |
|
231 |
+ } |
|
232 |
+ |
|
233 |
+ // Detect annotations on the class |
|
234 |
+ if (false !== $doc = $refl->getDocComment()) { |
|
235 |
+ foreach (['final', 'deprecated', 'internal'] as $annotation) { |
|
236 |
+ if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) { |
|
237 |
+ self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : ''; |
|
238 |
+ } |
|
239 |
+ } |
|
240 |
+ |
|
241 |
+ if ($refl->isInterface() && false !== strpos($doc, 'method') && preg_match_all('#\n \* @method\s+(static\s+)?+(?:[\w\|&\[\]\\\]+\s+)?(\w+(?:\s*\([^\)]*\))?)+(.+?([[:punct:]]\s*)?)?(?=\r?\n \*(?: @|/$|\r?\n))#', $doc, $notice, PREG_SET_ORDER)) { |
|
242 |
+ foreach ($notice as $method) { |
|
243 |
+ $static = '' !== $method[1]; |
|
244 |
+ $name = $method[2]; |
|
245 |
+ $description = $method[3] ?? null; |
|
246 |
+ if (false === strpos($name, '(')) { |
|
247 |
+ $name .= '()'; |
|
248 |
+ } |
|
249 |
+ if (null !== $description) { |
|
250 |
+ $description = trim($description); |
|
251 |
+ if (!isset($method[4])) { |
|
252 |
+ $description .= '.'; |
|
253 |
+ } |
|
254 |
+ } |
|
255 |
+ self::$method[$class][] = [$class, $name, $static, $description]; |
|
256 |
+ } |
|
257 |
+ } |
|
258 |
+ } |
|
259 |
+ |
|
260 |
+ $parent = get_parent_class($class); |
|
261 |
+ $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent); |
|
262 |
+ if ($parent) { |
|
263 |
+ $parentAndOwnInterfaces[$parent] = $parent; |
|
264 |
+ |
|
265 |
+ if (!isset(self::$checkedClasses[$parent])) { |
|
266 |
+ $this->checkClass($parent); |
|
267 |
+ } |
|
268 |
+ |
|
269 |
+ if (isset(self::$final[$parent])) { |
|
270 |
+ $deprecations[] = sprintf('The "%s" class is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $parent, self::$final[$parent], $class); |
|
271 |
+ } |
|
272 |
+ } |
|
273 |
+ |
|
274 |
+ // Detect if the parent is annotated |
|
275 |
+ foreach ($parentAndOwnInterfaces + class_uses($class, false) as $use) { |
|
276 |
+ if (!isset(self::$checkedClasses[$use])) { |
|
277 |
+ $this->checkClass($use); |
|
278 |
+ } |
|
279 |
+ if (isset(self::$deprecated[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len) && !isset(self::$deprecated[$class])) { |
|
280 |
+ $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait'); |
|
281 |
+ $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses'); |
|
282 |
+ |
|
283 |
+ $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]); |
|
284 |
+ } |
|
285 |
+ if (isset(self::$internal[$use]) && strncmp($ns, str_replace('_', '\\', $use), $len)) { |
|
286 |
+ $deprecations[] = sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $class); |
|
287 |
+ } |
|
288 |
+ if (isset(self::$method[$use])) { |
|
289 |
+ if ($refl->isAbstract()) { |
|
290 |
+ if (isset(self::$method[$class])) { |
|
291 |
+ self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]); |
|
292 |
+ } else { |
|
293 |
+ self::$method[$class] = self::$method[$use]; |
|
294 |
+ } |
|
295 |
+ } elseif (!$refl->isInterface()) { |
|
296 |
+ $hasCall = $refl->hasMethod('__call'); |
|
297 |
+ $hasStaticCall = $refl->hasMethod('__callStatic'); |
|
298 |
+ foreach (self::$method[$use] as $method) { |
|
299 |
+ list($interface, $name, $static, $description) = $method; |
|
300 |
+ if ($static ? $hasStaticCall : $hasCall) { |
|
301 |
+ continue; |
|
302 |
+ } |
|
303 |
+ $realName = substr($name, 0, strpos($name, '(')); |
|
304 |
+ if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) { |
|
305 |
+ $deprecations[] = sprintf('Class "%s" should implement method "%s::%s"%s', $class, ($static ? 'static ' : '').$interface, $name, null == $description ? '.' : ': '.$description); |
|
306 |
+ } |
|
307 |
+ } |
|
308 |
+ } |
|
309 |
+ } |
|
310 |
+ } |
|
311 |
+ |
|
312 |
+ if (trait_exists($class)) { |
|
313 |
+ return $deprecations; |
|
314 |
+ } |
|
315 |
+ |
|
316 |
+ // Inherit @final, @internal and @param annotations for methods |
|
317 |
+ self::$finalMethods[$class] = []; |
|
318 |
+ self::$internalMethods[$class] = []; |
|
319 |
+ self::$annotatedParameters[$class] = []; |
|
320 |
+ foreach ($parentAndOwnInterfaces as $use) { |
|
321 |
+ foreach (['finalMethods', 'internalMethods', 'annotatedParameters'] as $property) { |
|
322 |
+ if (isset(self::${$property}[$use])) { |
|
323 |
+ self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use]; |
|
324 |
+ } |
|
325 |
+ } |
|
326 |
+ } |
|
327 |
+ |
|
328 |
+ foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { |
|
329 |
+ if ($method->class !== $class) { |
|
330 |
+ continue; |
|
331 |
+ } |
|
332 |
+ |
|
333 |
+ if ($parent && isset(self::$finalMethods[$parent][$method->name])) { |
|
334 |
+ list($declaringClass, $message) = self::$finalMethods[$parent][$method->name]; |
|
335 |
+ $deprecations[] = sprintf('The "%s::%s()" method is considered final%s. It may change without further notice as of its next major version. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class); |
|
336 |
+ } |
|
337 |
+ |
|
338 |
+ if (isset(self::$internalMethods[$class][$method->name])) { |
|
339 |
+ list($declaringClass, $message) = self::$internalMethods[$class][$method->name]; |
|
340 |
+ if (strncmp($ns, $declaringClass, $len)) { |
|
341 |
+ $deprecations[] = sprintf('The "%s::%s()" method is considered internal%s. It may change without further notice. You should not extend it from "%s".', $declaringClass, $method->name, $message, $class); |
|
342 |
+ } |
|
343 |
+ } |
|
344 |
+ |
|
345 |
+ // To read method annotations |
|
346 |
+ $doc = $method->getDocComment(); |
|
347 |
+ |
|
348 |
+ if (isset(self::$annotatedParameters[$class][$method->name])) { |
|
349 |
+ $definedParameters = []; |
|
350 |
+ foreach ($method->getParameters() as $parameter) { |
|
351 |
+ $definedParameters[$parameter->name] = true; |
|
352 |
+ } |
|
353 |
+ |
|
354 |
+ foreach (self::$annotatedParameters[$class][$method->name] as $parameterName => $deprecation) { |
|
355 |
+ if (!isset($definedParameters[$parameterName]) && !($doc && preg_match("/\\n\\s+\\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\\\${$parameterName}\\b/", $doc))) { |
|
356 |
+ $deprecations[] = sprintf($deprecation, $class); |
|
357 |
+ } |
|
358 |
+ } |
|
359 |
+ } |
|
360 |
+ |
|
361 |
+ if (!$doc) { |
|
362 |
+ continue; |
|
363 |
+ } |
|
364 |
+ |
|
365 |
+ $finalOrInternal = false; |
|
366 |
+ |
|
367 |
+ foreach (['final', 'internal'] as $annotation) { |
|
368 |
+ if (false !== strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$|\r?\n)#s', $doc, $notice)) { |
|
369 |
+ $message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : ''; |
|
370 |
+ self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message]; |
|
371 |
+ $finalOrInternal = true; |
|
372 |
+ } |
|
373 |
+ } |
|
374 |
+ |
|
375 |
+ if ($finalOrInternal || $method->isConstructor() || false === strpos($doc, '@param') || StatelessInvocation::class === $class) { |
|
376 |
+ continue; |
|
377 |
+ } |
|
378 |
+ if (!preg_match_all('#\n\s+\* @param +((?(?!callable *\().*?|callable *\(.*\).*?))(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, PREG_SET_ORDER)) { |
|
379 |
+ continue; |
|
380 |
+ } |
|
381 |
+ if (!isset(self::$annotatedParameters[$class][$method->name])) { |
|
382 |
+ $definedParameters = []; |
|
383 |
+ foreach ($method->getParameters() as $parameter) { |
|
384 |
+ $definedParameters[$parameter->name] = true; |
|
385 |
+ } |
|
386 |
+ } |
|
387 |
+ foreach ($matches as list(, $parameterType, $parameterName)) { |
|
388 |
+ if (!isset($definedParameters[$parameterName])) { |
|
389 |
+ $parameterType = trim($parameterType); |
|
390 |
+ self::$annotatedParameters[$class][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $method->class); |
|
391 |
+ } |
|
392 |
+ } |
|
393 |
+ } |
|
394 |
+ |
|
395 |
+ return $deprecations; |
|
396 |
+ } |
|
397 |
+ |
|
398 |
+ public function checkCase(\ReflectionClass $refl, $file, $class) |
|
399 |
+ { |
|
400 |
+ $real = explode('\\', $class.strrchr($file, '.')); |
|
401 |
+ $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file)); |
|
402 |
+ |
|
403 |
+ $i = \count($tail) - 1; |
|
404 |
+ $j = \count($real) - 1; |
|
405 |
+ |
|
406 |
+ while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { |
|
407 |
+ --$i; |
|
408 |
+ --$j; |
|
409 |
+ } |
|
410 |
+ |
|
411 |
+ array_splice($tail, 0, $i + 1); |
|
412 |
+ |
|
413 |
+ if (!$tail) { |
|
414 |
+ return; |
|
415 |
+ } |
|
416 |
+ |
|
417 |
+ $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail); |
|
418 |
+ $tailLen = \strlen($tail); |
|
419 |
+ $real = $refl->getFileName(); |
|
420 |
+ |
|
421 |
+ if (2 === self::$caseCheck) { |
|
422 |
+ $real = $this->darwinRealpath($real); |
|
423 |
+ } |
|
424 |
+ |
|
425 |
+ if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) |
|
426 |
+ && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) |
|
427 |
+ ) { |
|
428 |
+ return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)]; |
|
429 |
+ } |
|
430 |
+ } |
|
431 |
+ |
|
432 |
+ /** |
|
433 |
+ * `realpath` on MacOSX doesn't normalize the case of characters. |
|
434 |
+ */ |
|
435 |
+ private function darwinRealpath($real) |
|
436 |
+ { |
|
437 |
+ $i = 1 + strrpos($real, '/'); |
|
438 |
+ $file = substr($real, $i); |
|
439 |
+ $real = substr($real, 0, $i); |
|
440 |
+ |
|
441 |
+ if (isset(self::$darwinCache[$real])) { |
|
442 |
+ $kDir = $real; |
|
443 |
+ } else { |
|
444 |
+ $kDir = strtolower($real); |
|
445 |
+ |
|
446 |
+ if (isset(self::$darwinCache[$kDir])) { |
|
447 |
+ $real = self::$darwinCache[$kDir][0]; |
|
448 |
+ } else { |
|
449 |
+ $dir = getcwd(); |
|
450 |
+ chdir($real); |
|
451 |
+ $real = getcwd().'/'; |
|
452 |
+ chdir($dir); |
|
453 |
+ |
|
454 |
+ $dir = $real; |
|
455 |
+ $k = $kDir; |
|
456 |
+ $i = \strlen($dir) - 1; |
|
457 |
+ while (!isset(self::$darwinCache[$k])) { |
|
458 |
+ self::$darwinCache[$k] = [$dir, []]; |
|
459 |
+ self::$darwinCache[$dir] = &self::$darwinCache[$k]; |
|
460 |
+ |
|
461 |
+ while ('/' !== $dir[--$i]) { |
|
462 |
+ } |
|
463 |
+ $k = substr($k, 0, ++$i); |
|
464 |
+ $dir = substr($dir, 0, $i--); |
|
465 |
+ } |
|
466 |
+ } |
|
467 |
+ } |
|
468 |
+ |
|
469 |
+ $dirFiles = self::$darwinCache[$kDir][1]; |
|
470 |
+ |
|
471 |
+ if (!isset($dirFiles[$file]) && ') : eval()\'d code' === substr($file, -17)) { |
|
472 |
+ // Get the file name from "file_name.php(123) : eval()'d code" |
|
473 |
+ $file = substr($file, 0, strrpos($file, '(', -17)); |
|
474 |
+ } |
|
475 |
+ |
|
476 |
+ if (isset($dirFiles[$file])) { |
|
477 |
+ return $real .= $dirFiles[$file]; |
|
478 |
+ } |
|
479 |
+ |
|
480 |
+ $kFile = strtolower($file); |
|
481 |
+ |
|
482 |
+ if (!isset($dirFiles[$kFile])) { |
|
483 |
+ foreach (scandir($real, 2) as $f) { |
|
484 |
+ if ('.' !== $f[0]) { |
|
485 |
+ $dirFiles[$f] = $f; |
|
486 |
+ if ($f === $file) { |
|
487 |
+ $kFile = $k = $file; |
|
488 |
+ } elseif ($f !== $k = strtolower($f)) { |
|
489 |
+ $dirFiles[$k] = $f; |
|
490 |
+ } |
|
491 |
+ } |
|
492 |
+ } |
|
493 |
+ self::$darwinCache[$kDir][1] = $dirFiles; |
|
494 |
+ } |
|
495 |
+ |
|
496 |
+ return $real .= $dirFiles[$kFile]; |
|
497 |
+ } |
|
498 |
+ |
|
499 |
+ /** |
|
500 |
+ * `class_implements` includes interfaces from the parents so we have to manually exclude them. |
|
501 |
+ * |
|
502 |
+ * @param string $class |
|
503 |
+ * @param string|false $parent |
|
504 |
+ * |
|
505 |
+ * @return string[] |
|
506 |
+ */ |
|
507 |
+ private function getOwnInterfaces($class, $parent) |
|
508 |
+ { |
|
509 |
+ $ownInterfaces = class_implements($class, false); |
|
510 |
+ |
|
511 |
+ if ($parent) { |
|
512 |
+ foreach (class_implements($parent, false) as $interface) { |
|
513 |
+ unset($ownInterfaces[$interface]); |
|
514 |
+ } |
|
515 |
+ } |
|
516 |
+ |
|
517 |
+ foreach ($ownInterfaces as $interface) { |
|
518 |
+ foreach (class_implements($interface) as $interface) { |
|
519 |
+ unset($ownInterfaces[$interface]); |
|
520 |
+ } |
|
521 |
+ } |
|
522 |
+ |
|
523 |
+ return $ownInterfaces; |
|
524 |
+ } |
|
525 |
+} |