comparison vendor/symfony/debug/DebugClassLoader.php @ 17:129ea1e6d783

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:21:36 +0000
parents 5fb285c0d0e3
children af1871eacc83
comparison
equal deleted inserted replaced
16:c2387f117808 17:129ea1e6d783
24 */ 24 */
25 class DebugClassLoader 25 class DebugClassLoader
26 { 26 {
27 private $classLoader; 27 private $classLoader;
28 private $isFinder; 28 private $isFinder;
29 private $loaded = array(); 29 private $loaded = [];
30 private static $caseCheck; 30 private static $caseCheck;
31 private static $checkedClasses = array(); 31 private static $checkedClasses = [];
32 private static $final = array(); 32 private static $final = [];
33 private static $finalMethods = array(); 33 private static $finalMethods = [];
34 private static $deprecated = array(); 34 private static $deprecated = [];
35 private static $internal = array(); 35 private static $internal = [];
36 private static $internalMethods = array(); 36 private static $internalMethods = [];
37 private static $php7Reserved = array('int' => 1, 'float' => 1, 'bool' => 1, 'string' => 1, 'true' => 1, 'false' => 1, 'null' => 1); 37 private static $php7Reserved = ['int' => 1, 'float' => 1, 'bool' => 1, 'string' => 1, 'true' => 1, 'false' => 1, 'null' => 1];
38 private static $darwinCache = array('/' => array('/', array())); 38 private static $darwinCache = ['/' => ['/', []]];
39 39
40 public function __construct(callable $classLoader) 40 public function __construct(callable $classLoader)
41 { 41 {
42 $this->classLoader = $classLoader; 42 $this->classLoader = $classLoader;
43 $this->isFinder = is_array($classLoader) && method_exists($classLoader[0], 'findFile'); 43 $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile');
44 44
45 if (!isset(self::$caseCheck)) { 45 if (!isset(self::$caseCheck)) {
46 $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), DIRECTORY_SEPARATOR); 46 $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR);
47 $i = strrpos($file, DIRECTORY_SEPARATOR); 47 $i = strrpos($file, \DIRECTORY_SEPARATOR);
48 $dir = substr($file, 0, 1 + $i); 48 $dir = substr($file, 0, 1 + $i);
49 $file = substr($file, 1 + $i); 49 $file = substr($file, 1 + $i);
50 $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); 50 $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file);
51 $test = realpath($dir.$test); 51 $test = realpath($dir.$test);
52 52
53 if (false === $test || false === $i) { 53 if (false === $test || false === $i) {
54 // filesystem is case sensitive 54 // filesystem is case sensitive
55 self::$caseCheck = 0; 55 self::$caseCheck = 0;
56 } elseif (substr($test, -strlen($file)) === $file) { 56 } elseif (substr($test, -\strlen($file)) === $file) {
57 // filesystem is case insensitive and realpath() normalizes the case of characters 57 // filesystem is case insensitive and realpath() normalizes the case of characters
58 self::$caseCheck = 1; 58 self::$caseCheck = 1;
59 } elseif (false !== stripos(PHP_OS, 'darwin')) { 59 } elseif (false !== stripos(PHP_OS, 'darwin')) {
60 // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters 60 // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters
61 self::$caseCheck = 2; 61 self::$caseCheck = 2;
83 { 83 {
84 // Ensures we don't hit https://bugs.php.net/42098 84 // Ensures we don't hit https://bugs.php.net/42098
85 class_exists('Symfony\Component\Debug\ErrorHandler'); 85 class_exists('Symfony\Component\Debug\ErrorHandler');
86 class_exists('Psr\Log\LogLevel'); 86 class_exists('Psr\Log\LogLevel');
87 87
88 if (!is_array($functions = spl_autoload_functions())) { 88 if (!\is_array($functions = spl_autoload_functions())) {
89 return; 89 return;
90 } 90 }
91 91
92 foreach ($functions as $function) { 92 foreach ($functions as $function) {
93 spl_autoload_unregister($function); 93 spl_autoload_unregister($function);
94 } 94 }
95 95
96 foreach ($functions as $function) { 96 foreach ($functions as $function) {
97 if (!is_array($function) || !$function[0] instanceof self) { 97 if (!\is_array($function) || !$function[0] instanceof self) {
98 $function = array(new static($function), 'loadClass'); 98 $function = [new static($function), 'loadClass'];
99 } 99 }
100 100
101 spl_autoload_register($function); 101 spl_autoload_register($function);
102 } 102 }
103 } 103 }
105 /** 105 /**
106 * Disables the wrapping. 106 * Disables the wrapping.
107 */ 107 */
108 public static function disable() 108 public static function disable()
109 { 109 {
110 if (!is_array($functions = spl_autoload_functions())) { 110 if (!\is_array($functions = spl_autoload_functions())) {
111 return; 111 return;
112 } 112 }
113 113
114 foreach ($functions as $function) { 114 foreach ($functions as $function) {
115 spl_autoload_unregister($function); 115 spl_autoload_unregister($function);
116 } 116 }
117 117
118 foreach ($functions as $function) { 118 foreach ($functions as $function) {
119 if (is_array($function) && $function[0] instanceof self) { 119 if (\is_array($function) && $function[0] instanceof self) {
120 $function = $function[0]->getClassLoader(); 120 $function = $function[0]->getClassLoader();
121 } 121 }
122 122
123 spl_autoload_register($function); 123 spl_autoload_register($function);
124 } 124 }
125 }
126
127 /**
128 * @return string|null
129 */
130 public function findFile($class)
131 {
132 return $this->isFinder ? $this->classLoader[0]->findFile($class) ?: null : null;
125 } 133 }
126 134
127 /** 135 /**
128 * Loads the given class or interface. 136 * Loads the given class or interface.
129 * 137 *
130 * @param string $class The name of the class 138 * @param string $class The name of the class
131 * 139 *
132 * @return bool|null True, if loaded
133 *
134 * @throws \RuntimeException 140 * @throws \RuntimeException
135 */ 141 */
136 public function loadClass($class) 142 public function loadClass($class)
137 { 143 {
138 $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); 144 $e = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
139 145
140 try { 146 try {
141 if ($this->isFinder && !isset($this->loaded[$class])) { 147 if ($this->isFinder && !isset($this->loaded[$class])) {
142 $this->loaded[$class] = true; 148 $this->loaded[$class] = true;
143 if ($file = $this->classLoader[0]->findFile($class) ?: false) { 149 if (!$file = $this->classLoader[0]->findFile($class) ?: false) {
144 $wasCached = \function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file); 150 // no-op
145 151 } elseif (\function_exists('opcache_is_script_cached') && @opcache_is_script_cached($file)) {
146 require $file; 152 require $file;
147 153
148 if ($wasCached) { 154 return;
149 return; 155 } else {
150 } 156 require $file;
151 } 157 }
152 } else { 158 } else {
153 call_user_func($this->classLoader, $class); 159 \call_user_func($this->classLoader, $class);
154 $file = false; 160 $file = false;
155 } 161 }
156 } finally { 162 } finally {
157 error_reporting($e); 163 error_reporting($e);
158 } 164 }
182 188
183 if ($name !== $class && 0 === \strcasecmp($name, $class)) { 189 if ($name !== $class && 0 === \strcasecmp($name, $class)) {
184 throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); 190 throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
185 } 191 }
186 192
187 // Don't trigger deprecations for classes in the same vendor 193 $deprecations = $this->checkAnnotations($refl, $name);
188 if (2 > $len = 1 + (\strpos($name, '\\') ?: \strpos($name, '_'))) { 194
189 $len = 0; 195 if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
190 $ns = ''; 196 $deprecations[] = sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName());
197 }
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+\*(?: @|/$)#s', $doc, $notice)) {
237 self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
238 }
239 }
240 }
241
242 $parent = \get_parent_class($class);
243 $parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
244 if ($parent) {
245 $parentAndOwnInterfaces[$parent] = $parent;
246
247 if (!isset(self::$checkedClasses[$parent])) {
248 $this->checkClass($parent);
249 }
250
251 if (isset(self::$final[$parent])) {
252 $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);
253 }
254 }
255
256 // Detect if the parent is annotated
257 foreach ($parentAndOwnInterfaces + \class_uses($class, false) as $use) {
258 if (!isset(self::$checkedClasses[$use])) {
259 $this->checkClass($use);
260 }
261 if (isset(self::$deprecated[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
262 $type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
263 $verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
264
265 $deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
266 }
267 if (isset(self::$internal[$use]) && \strncmp($ns, \str_replace('_', '\\', $use), $len)) {
268 $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);
269 }
270 }
271
272 if (\trait_exists($class)) {
273 return $deprecations;
274 }
275
276 // Inherit @final and @internal annotations for methods
277 self::$finalMethods[$class] = [];
278 self::$internalMethods[$class] = [];
279 foreach ($parentAndOwnInterfaces as $use) {
280 foreach (['finalMethods', 'internalMethods'] as $property) {
281 if (isset(self::${$property}[$use])) {
282 self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
283 }
284 }
285 }
286
287 foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
288 if ($method->class !== $class) {
289 continue;
290 }
291
292 if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
293 list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
294 $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);
295 }
296
297 if (isset(self::$internalMethods[$class][$method->name])) {
298 list($declaringClass, $message) = self::$internalMethods[$class][$method->name];
299 if (\strncmp($ns, $declaringClass, $len)) {
300 $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);
301 }
302 }
303
304 // Detect method annotations
305 if (false === $doc = $method->getDocComment()) {
306 continue;
307 }
308
309 foreach (['final', 'internal'] as $annotation) {
310 if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
311 $message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
312 self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
313 }
314 }
315 }
316
317 return $deprecations;
318 }
319
320 public function checkCase(\ReflectionClass $refl, $file, $class)
321 {
322 $real = explode('\\', $class.strrchr($file, '.'));
323 $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));
324
325 $i = \count($tail) - 1;
326 $j = \count($real) - 1;
327
328 while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
329 --$i;
330 --$j;
331 }
332
333 array_splice($tail, 0, $i + 1);
334
335 if (!$tail) {
336 return;
337 }
338
339 $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
340 $tailLen = \strlen($tail);
341 $real = $refl->getFileName();
342
343 if (2 === self::$caseCheck) {
344 $real = $this->darwinRealpath($real);
345 }
346
347 if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
348 && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
349 ) {
350 return [substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)];
351 }
352 }
353
354 /**
355 * `realpath` on MacOSX doesn't normalize the case of characters.
356 */
357 private function darwinRealpath($real)
358 {
359 $i = 1 + strrpos($real, '/');
360 $file = substr($real, $i);
361 $real = substr($real, 0, $i);
362
363 if (isset(self::$darwinCache[$real])) {
364 $kDir = $real;
365 } else {
366 $kDir = strtolower($real);
367
368 if (isset(self::$darwinCache[$kDir])) {
369 $real = self::$darwinCache[$kDir][0];
191 } else { 370 } else {
192 $ns = \substr($name, 0, $len); 371 $dir = getcwd();
193 } 372 chdir($real);
194 373 $real = getcwd().'/';
195 // Detect annotations on the class 374 chdir($dir);
196 if (false !== $doc = $refl->getDocComment()) { 375
197 foreach (array('final', 'deprecated', 'internal') as $annotation) { 376 $dir = $real;
198 if (false !== \strpos($doc, $annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) { 377 $k = $kDir;
199 self::${$annotation}[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; 378 $i = \strlen($dir) - 1;
379 while (!isset(self::$darwinCache[$k])) {
380 self::$darwinCache[$k] = [$dir, []];
381 self::$darwinCache[$dir] = &self::$darwinCache[$k];
382
383 while ('/' !== $dir[--$i]) {
200 } 384 }
201 } 385 $k = substr($k, 0, ++$i);
202 } 386 $dir = substr($dir, 0, $i--);
203 387 }
204 $parentAndTraits = \class_uses($name, false); 388 }
205 if ($parent = \get_parent_class($class)) { 389 }
206 $parentAndTraits[] = $parent; 390
207 391 $dirFiles = self::$darwinCache[$kDir][1];
208 if (!isset(self::$checkedClasses[$parent])) { 392
209 $this->checkClass($parent); 393 if (isset($dirFiles[$file])) {
210 } 394 return $real .= $dirFiles[$file];
211 395 }
212 if (isset(self::$final[$parent])) { 396
213 @trigger_error(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], $name), E_USER_DEPRECATED); 397 $kFile = strtolower($file);
214 } 398
215 } 399 if (!isset($dirFiles[$kFile])) {
216 400 foreach (scandir($real, 2) as $f) {
217 // Detect if the parent is annotated 401 if ('.' !== $f[0]) {
218 foreach ($parentAndTraits + $this->getOwnInterfaces($name, $parent) as $use) { 402 $dirFiles[$f] = $f;
219 if (!isset(self::$checkedClasses[$use])) { 403 if ($f === $file) {
220 $this->checkClass($use); 404 $kFile = $k = $file;
221 } 405 } elseif ($f !== $k = strtolower($f)) {
222 if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) { 406 $dirFiles[$k] = $f;
223 $type = class_exists($name, false) ? 'class' : (interface_exists($name, false) ? 'interface' : 'trait');
224 $verb = class_exists($use, false) || interface_exists($name, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
225
226 @trigger_error(sprintf('The "%s" %s %s "%s" that is deprecated%s.', $name, $type, $verb, $use, self::$deprecated[$use]), E_USER_DEPRECATED);
227 }
228 if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
229 @trigger_error(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], $name), E_USER_DEPRECATED);
230 }
231 }
232
233 // Inherit @final and @internal annotations for methods
234 self::$finalMethods[$name] = array();
235 self::$internalMethods[$name] = array();
236 foreach ($parentAndTraits as $use) {
237 foreach (array('finalMethods', 'internalMethods') as $property) {
238 if (isset(self::${$property}[$use])) {
239 self::${$property}[$name] = self::${$property}[$name] ? self::${$property}[$use] + self::${$property}[$name] : self::${$property}[$use];
240 } 407 }
241 } 408 }
242 } 409 }
243 410 self::$darwinCache[$kDir][1] = $dirFiles;
244 $isClass = \class_exists($name, false); 411 }
245 foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { 412
246 if ($method->class !== $name) { 413 return $real .= $dirFiles[$kFile];
247 continue;
248 }
249
250 // Method from a trait
251 if ($method->getFilename() !== $refl->getFileName()) {
252 continue;
253 }
254
255 if ($isClass && $parent && isset(self::$finalMethods[$parent][$method->name])) {
256 list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
257 @trigger_error(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, $name), E_USER_DEPRECATED);
258 }
259
260 foreach ($parentAndTraits as $use) {
261 if (isset(self::$internalMethods[$use][$method->name])) {
262 list($declaringClass, $message) = self::$internalMethods[$use][$method->name];
263 if (\strncmp($ns, $declaringClass, $len)) {
264 @trigger_error(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, $name), E_USER_DEPRECATED);
265 }
266 }
267 }
268
269 // Detect method annotations
270 if (false === $doc = $method->getDocComment()) {
271 continue;
272 }
273
274 foreach (array('final', 'internal') as $annotation) {
275 if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
276 $message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
277 self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
278 }
279 }
280 }
281
282 if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
283 @trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED);
284 }
285 }
286
287 if ($file) {
288 if (!$exists) {
289 if (false !== strpos($class, '/')) {
290 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));
291 }
292
293 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));
294 }
295 if (self::$caseCheck) {
296 $real = explode('\\', $class.strrchr($file, '.'));
297 $tail = explode(DIRECTORY_SEPARATOR, str_replace('/', DIRECTORY_SEPARATOR, $file));
298
299 $i = count($tail) - 1;
300 $j = count($real) - 1;
301
302 while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
303 --$i;
304 --$j;
305 }
306
307 array_splice($tail, 0, $i + 1);
308 }
309 if (self::$caseCheck && $tail) {
310 $tail = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $tail);
311 $tailLen = strlen($tail);
312 $real = $refl->getFileName();
313
314 if (2 === self::$caseCheck) {
315 // realpath() on MacOSX doesn't normalize the case of characters
316
317 $i = 1 + strrpos($real, '/');
318 $file = substr($real, $i);
319 $real = substr($real, 0, $i);
320
321 if (isset(self::$darwinCache[$real])) {
322 $kDir = $real;
323 } else {
324 $kDir = strtolower($real);
325
326 if (isset(self::$darwinCache[$kDir])) {
327 $real = self::$darwinCache[$kDir][0];
328 } else {
329 $dir = getcwd();
330 chdir($real);
331 $real = getcwd().'/';
332 chdir($dir);
333
334 $dir = $real;
335 $k = $kDir;
336 $i = strlen($dir) - 1;
337 while (!isset(self::$darwinCache[$k])) {
338 self::$darwinCache[$k] = array($dir, array());
339 self::$darwinCache[$dir] = &self::$darwinCache[$k];
340
341 while ('/' !== $dir[--$i]) {
342 }
343 $k = substr($k, 0, ++$i);
344 $dir = substr($dir, 0, $i--);
345 }
346 }
347 }
348
349 $dirFiles = self::$darwinCache[$kDir][1];
350
351 if (isset($dirFiles[$file])) {
352 $kFile = $file;
353 } else {
354 $kFile = strtolower($file);
355
356 if (!isset($dirFiles[$kFile])) {
357 foreach (scandir($real, 2) as $f) {
358 if ('.' !== $f[0]) {
359 $dirFiles[$f] = $f;
360 if ($f === $file) {
361 $kFile = $k = $file;
362 } elseif ($f !== $k = strtolower($f)) {
363 $dirFiles[$k] = $f;
364 }
365 }
366 }
367 self::$darwinCache[$kDir][1] = $dirFiles;
368 }
369 }
370
371 $real .= $dirFiles[$kFile];
372 }
373
374 if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
375 && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
376 ) {
377 throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1)));
378 }
379 }
380 }
381 } 414 }
382 415
383 /** 416 /**
384 * `class_implements` includes interfaces from the parents so we have to manually exclude them. 417 * `class_implements` includes interfaces from the parents so we have to manually exclude them.
385 * 418 *