Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9bc774c

Browse filesBrowse files
bug #28553 [Debug] Fix false-positive "MicroKernelTrait::loadRoutes()" method is considered internal" (nicolas-grekas)
This PR was merged into the 3.4 branch. Discussion ---------- [Debug] Fix false-positive "MicroKernelTrait::loadRoutes()" method is considered internal" | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes (fixing and unreleased issue) | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #28549 | License | MIT | Doc PR | - Introduced in #28411 Just a failing test for now. Commits ------- 46c4f71 [Debug] Fix false-positive "MicroKernelTrait::loadRoutes()" method is considered internal"
2 parents 3e47e9c + 46c4f71 commit 9bc774c
Copy full SHA for 9bc774c

File tree

Expand file treeCollapse file tree

3 files changed

+215
-159
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+215
-159
lines changed

‎src/Symfony/Component/Debug/DebugClassLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/DebugClassLoader.php
+184-157Lines changed: 184 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,6 @@ public static function disable()
129129
*
130130
* @param string $class The name of the class
131131
*
132-
* @return bool|null True, if loaded
133-
*
134132
* @throws \RuntimeException
135133
*/
136134
public function loadClass($class)
@@ -184,198 +182,227 @@ private function checkClass($class, $file = null)
184182
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
185183
}
186184

187-
// Don't trigger deprecations for classes in the same vendor
188-
if (2 > $len = 1 + (\strpos($name, '\\') ?: \strpos($name, '_'))) {
189-
$len = 0;
190-
$ns = '';
191-
} else {
192-
$ns = \substr($name, 0, $len);
185+
$deprecations = $this->checkAnnotations($refl, $name);
186+
187+
if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
188+
$deprecations[] = sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName());
193189
}
194190

195-
// Detect annotations on the class
196-
if (false !== $doc = $refl->getDocComment()) {
197-
foreach (array('final', 'deprecated', 'internal') as $annotation) {
198-
if (false !== \strpos($doc, $annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
199-
self::${$annotation}[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
200-
}
201-
}
191+
foreach ($deprecations as $message) {
192+
@trigger_error($message, E_USER_DEPRECATED);
202193
}
194+
}
203195

204-
$parentAndTraits = \class_uses($name, false);
205-
if ($parent = \get_parent_class($class)) {
206-
$parentAndTraits[] = $parent;
196+
if (!$file) {
197+
return;
198+
}
207199

208-
if (!isset(self::$checkedClasses[$parent])) {
209-
$this->checkClass($parent);
210-
}
200+
if (!$exists) {
201+
if (false !== strpos($class, '/')) {
202+
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));
203+
}
204+
205+
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));
206+
}
207+
208+
if (self::$caseCheck && $message = $this->checkCase($refl, $file, $class)) {
209+
throw new \RuntimeException(sprintf('Case mismatch between class and real file names: "%s" vs "%s" in "%s".', $message[0], $message[1], $message[2]));
210+
}
211+
}
211212

212-
if (isset(self::$final[$parent])) {
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);
213+
public function checkAnnotations(\ReflectionClass $refl, $class)
214+
{
215+
$deprecations = array();
216+
217+
// Don't trigger deprecations for classes in the same vendor
218+
if (2 > $len = 1 + (\strpos($class, '\\') ?: \strpos($class, '_'))) {
219+
$len = 0;
220+
$ns = '';
221+
} else {
222+
$ns = \substr($class, 0, $len);
223+
}
224+
225+
// Detect annotations on the class
226+
if (false !== $doc = $refl->getDocComment()) {
227+
foreach (array('final', 'deprecated', 'internal') as $annotation) {
228+
if (false !== \strpos($doc, $annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
229+
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
214230
}
215231
}
232+
}
216233

217-
// Detect if the parent is annotated
218-
foreach ($parentAndTraits + $this->getOwnInterfaces($name, $parent) as $use) {
219-
if (!isset(self::$checkedClasses[$use])) {
220-
$this->checkClass($use);
221-
}
222-
if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) {
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');
234+
$parent = \get_parent_class($class);
235+
$parentAndOwnInterfaces = $this->getOwnInterfaces($class, $parent);
236+
if ($parent) {
237+
$parentAndOwnInterfaces[$parent] = $parent;
225238

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-
}
239+
if (!isset(self::$checkedClasses[$parent])) {
240+
$this->checkClass($parent);
231241
}
232242

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-
}
241-
}
243+
if (isset(self::$final[$parent])) {
244+
$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);
242245
}
246+
}
243247

244-
$isClass = \class_exists($name, false);
245-
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
246-
if ($method->class !== $name) {
247-
continue;
248-
}
248+
// Detect if the parent is annotated
249+
foreach ($parentAndOwnInterfaces + \class_uses($class, false) as $use) {
250+
if (!isset(self::$checkedClasses[$use])) {
251+
$this->checkClass($use);
252+
}
253+
if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) {
254+
$type = class_exists($class, false) ? 'class' : (interface_exists($class, false) ? 'interface' : 'trait');
255+
$verb = class_exists($use, false) || interface_exists($class, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
249256

250-
if ($isClass && $parent && isset(self::$finalMethods[$parent][$method->name])) {
251-
list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
252-
@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);
253-
}
257+
$deprecations[] = sprintf('The "%s" %s %s "%s" that is deprecated%s.', $class, $type, $verb, $use, self::$deprecated[$use]);
258+
}
259+
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
260+
$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);
261+
}
262+
}
254263

255-
if (isset(self::$internalMethods[$name][$method->name])) {
256-
list($declaringClass, $message) = self::$internalMethods[$name][$method->name];
257-
if (\strncmp($ns, $declaringClass, $len)) {
258-
@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);
259-
}
260-
}
264+
if (\trait_exists($class)) {
265+
return $deprecations;
266+
}
261267

262-
// Method from a trait
263-
if ($method->getFilename() !== $refl->getFileName()) {
264-
continue;
268+
// Inherit @final and @internal annotations for methods
269+
self::$finalMethods[$class] = array();
270+
self::$internalMethods[$class] = array();
271+
foreach ($parentAndOwnInterfaces as $use) {
272+
foreach (array('finalMethods', 'internalMethods') as $property) {
273+
if (isset(self::${$property}[$use])) {
274+
self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
265275
}
276+
}
277+
}
266278

267-
// Detect method annotations
268-
if (false === $doc = $method->getDocComment()) {
269-
continue;
270-
}
279+
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
280+
if ($method->class !== $class) {
281+
continue;
282+
}
271283

272-
foreach (array('final', 'internal') as $annotation) {
273-
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
274-
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
275-
self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
276-
}
284+
if ($parent && isset(self::$finalMethods[$parent][$method->name])) {
285+
list($declaringClass, $message) = self::$finalMethods[$parent][$method->name];
286+
$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);
287+
}
288+
289+
if (isset(self::$internalMethods[$class][$method->name])) {
290+
list($declaringClass, $message) = self::$internalMethods[$class][$method->name];
291+
if (\strncmp($ns, $declaringClass, $len)) {
292+
$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);
277293
}
278294
}
279295

280-
if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
281-
@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);
296+
// Detect method annotations
297+
if (false === $doc = $method->getDocComment()) {
298+
continue;
282299
}
283-
}
284300

285-
if ($file) {
286-
if (!$exists) {
287-
if (false !== strpos($class, '/')) {
288-
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));
301+
foreach (array('final', 'internal') as $annotation) {
302+
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
303+
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
304+
self::${$annotation.'Methods'}[$class][$method->name] = array($class, $message);
289305
}
290-
291-
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));
292306
}
293-
if (self::$caseCheck) {
294-
$real = explode('\\', $class.strrchr($file, '.'));
295-
$tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));
307+
}
296308

297-
$i = \count($tail) - 1;
298-
$j = \count($real) - 1;
309+
return $deprecations;
310+
}
299311

300-
while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
301-
--$i;
302-
--$j;
303-
}
312+
public function checkCase(\ReflectionClass $refl, $file, $class)
313+
{
314+
$real = explode('\\', $class.strrchr($file, '.'));
315+
$tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file));
304316

305-
array_splice($tail, 0, $i + 1);
306-
}
307-
if (self::$caseCheck && $tail) {
308-
$tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
309-
$tailLen = \strlen($tail);
310-
$real = $refl->getFileName();
311-
312-
if (2 === self::$caseCheck) {
313-
// realpath() on MacOSX doesn't normalize the case of characters
314-
315-
$i = 1 + strrpos($real, '/');
316-
$file = substr($real, $i);
317-
$real = substr($real, 0, $i);
318-
319-
if (isset(self::$darwinCache[$real])) {
320-
$kDir = $real;
321-
} else {
322-
$kDir = strtolower($real);
323-
324-
if (isset(self::$darwinCache[$kDir])) {
325-
$real = self::$darwinCache[$kDir][0];
326-
} else {
327-
$dir = getcwd();
328-
chdir($real);
329-
$real = getcwd().'/';
330-
chdir($dir);
331-
332-
$dir = $real;
333-
$k = $kDir;
334-
$i = \strlen($dir) - 1;
335-
while (!isset(self::$darwinCache[$k])) {
336-
self::$darwinCache[$k] = array($dir, array());
337-
self::$darwinCache[$dir] = &self::$darwinCache[$k];
338-
339-
while ('/' !== $dir[--$i]) {
340-
}
341-
$k = substr($k, 0, ++$i);
342-
$dir = substr($dir, 0, $i--);
343-
}
344-
}
345-
}
317+
$i = \count($tail) - 1;
318+
$j = \count($real) - 1;
346319

347-
$dirFiles = self::$darwinCache[$kDir][1];
348-
349-
if (isset($dirFiles[$file])) {
350-
$kFile = $file;
351-
} else {
352-
$kFile = strtolower($file);
353-
354-
if (!isset($dirFiles[$kFile])) {
355-
foreach (scandir($real, 2) as $f) {
356-
if ('.' !== $f[0]) {
357-
$dirFiles[$f] = $f;
358-
if ($f === $file) {
359-
$kFile = $k = $file;
360-
} elseif ($f !== $k = strtolower($f)) {
361-
$dirFiles[$k] = $f;
362-
}
363-
}
364-
}
365-
self::$darwinCache[$kDir][1] = $dirFiles;
366-
}
367-
}
320+
while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) {
321+
--$i;
322+
--$j;
323+
}
324+
325+
array_splice($tail, 0, $i + 1);
368326

369-
$real .= $dirFiles[$kFile];
327+
if (!$tail) {
328+
return;
329+
}
330+
331+
$tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail);
332+
$tailLen = \strlen($tail);
333+
$real = $refl->getFileName();
334+
335+
if (2 === self::$caseCheck) {
336+
$real = $this->darwinRealpath($real);
337+
}
338+
339+
if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
340+
&& 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
341+
) {
342+
return array(substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1));
343+
}
344+
}
345+
346+
/**
347+
* `realpath` on MacOSX doesn't normalize the case of characters.
348+
*/
349+
private function darwinRealpath($real)
350+
{
351+
$i = 1 + strrpos($real, '/');
352+
$file = substr($real, $i);
353+
$real = substr($real, 0, $i);
354+
355+
if (isset(self::$darwinCache[$real])) {
356+
$kDir = $real;
357+
} else {
358+
$kDir = strtolower($real);
359+
360+
if (isset(self::$darwinCache[$kDir])) {
361+
$real = self::$darwinCache[$kDir][0];
362+
} else {
363+
$dir = getcwd();
364+
chdir($real);
365+
$real = getcwd().'/';
366+
chdir($dir);
367+
368+
$dir = $real;
369+
$k = $kDir;
370+
$i = \strlen($dir) - 1;
371+
while (!isset(self::$darwinCache[$k])) {
372+
self::$darwinCache[$k] = array($dir, array());
373+
self::$darwinCache[$dir] = &self::$darwinCache[$k];
374+
375+
while ('/' !== $dir[--$i]) {
376+
}
377+
$k = substr($k, 0, ++$i);
378+
$dir = substr($dir, 0, $i--);
370379
}
380+
}
381+
}
371382

372-
if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true)
373-
&& 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false)
374-
) {
375-
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)));
383+
$dirFiles = self::$darwinCache[$kDir][1];
384+
385+
if (isset($dirFiles[$file])) {
386+
return $real .= $dirFiles[$file];
387+
}
388+
389+
$kFile = strtolower($file);
390+
391+
if (!isset($dirFiles[$kFile])) {
392+
foreach (scandir($real, 2) as $f) {
393+
if ('.' !== $f[0]) {
394+
$dirFiles[$f] = $f;
395+
if ($f === $file) {
396+
$kFile = $k = $file;
397+
} elseif ($f !== $k = strtolower($f)) {
398+
$dirFiles[$k] = $f;
399+
}
376400
}
377401
}
402+
self::$darwinCache[$kDir][1] = $dirFiles;
378403
}
404+
405+
return $real .= $dirFiles[$kFile];
379406
}
380407

381408
/**

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.