[Debug] Skip DebugClassLoader checks for already parsed files
This commit is contained in:
parent
cd6690dacd
commit
f0c12234b9
@ -28,12 +28,13 @@ class DebugClassLoader
|
|||||||
private $isFinder;
|
private $isFinder;
|
||||||
private $loaded = array();
|
private $loaded = array();
|
||||||
private static $caseCheck;
|
private static $caseCheck;
|
||||||
|
private static $checkedClasses = array();
|
||||||
private static $final = array();
|
private static $final = array();
|
||||||
private static $finalMethods = array();
|
private static $finalMethods = array();
|
||||||
private static $deprecated = array();
|
private static $deprecated = array();
|
||||||
private static $internal = array();
|
private static $internal = array();
|
||||||
private static $internalMethods = array();
|
private static $internalMethods = array();
|
||||||
private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null');
|
private static $php7Reserved = array('int' => 1, 'float' => 1, 'bool' => 1, 'string' => 1, 'true' => 1, 'false' => 1, 'null' => 1);
|
||||||
private static $darwinCache = array('/' => array('/', array()));
|
private static $darwinCache = array('/' => array('/', array()));
|
||||||
|
|
||||||
public function __construct(callable $classLoader)
|
public function __construct(callable $classLoader)
|
||||||
@ -139,8 +140,14 @@ class DebugClassLoader
|
|||||||
try {
|
try {
|
||||||
if ($this->isFinder && !isset($this->loaded[$class])) {
|
if ($this->isFinder && !isset($this->loaded[$class])) {
|
||||||
$this->loaded[$class] = true;
|
$this->loaded[$class] = true;
|
||||||
if ($file = $this->classLoader[0]->findFile($class)) {
|
if ($file = $this->classLoader[0]->findFile($class) ?: false) {
|
||||||
|
$wasCached = \function_exists('opcache_is_script_cached') && opcache_is_script_cached($file);
|
||||||
|
|
||||||
require $file;
|
require $file;
|
||||||
|
|
||||||
|
if ($wasCached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call_user_func($this->classLoader, $class);
|
call_user_func($this->classLoader, $class);
|
||||||
@ -150,41 +157,58 @@ class DebugClassLoader
|
|||||||
error_reporting($e);
|
error_reporting($e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$exists = class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false);
|
$this->checkClass($class, $file);
|
||||||
|
}
|
||||||
|
|
||||||
if ($class && '\\' === $class[0]) {
|
private function checkClass($class, $file = null)
|
||||||
|
{
|
||||||
|
$exists = null === $file || \class_exists($class, false) || \interface_exists($class, false) || \trait_exists($class, false);
|
||||||
|
|
||||||
|
if (null !== $file && $class && '\\' === $class[0]) {
|
||||||
$class = substr($class, 1);
|
$class = substr($class, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($exists) {
|
if ($exists) {
|
||||||
|
if (isset(self::$checkedClasses[$class])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self::$checkedClasses[$class] = true;
|
||||||
|
|
||||||
$refl = new \ReflectionClass($class);
|
$refl = new \ReflectionClass($class);
|
||||||
|
if (null === $file && $refl->isInternal()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$name = $refl->getName();
|
$name = $refl->getName();
|
||||||
|
|
||||||
if ($name !== $class && 0 === strcasecmp($name, $class)) {
|
if ($name !== $class && 0 === \strcasecmp($name, $class)) {
|
||||||
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
|
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't trigger deprecations for classes in the same vendor
|
// Don't trigger deprecations for classes in the same vendor
|
||||||
if (2 > $len = 1 + (strpos($name, '\\') ?: strpos($name, '_'))) {
|
if (2 > $len = 1 + (\strpos($name, '\\') ?: \strpos($name, '_'))) {
|
||||||
$len = 0;
|
$len = 0;
|
||||||
$ns = '';
|
$ns = '';
|
||||||
} else {
|
} else {
|
||||||
$ns = substr($name, 0, $len);
|
$ns = \substr($name, 0, $len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect annotations on the class
|
// Detect annotations on the class
|
||||||
if (false !== $doc = $refl->getDocComment()) {
|
if (false !== $doc = $refl->getDocComment()) {
|
||||||
foreach (array('final', 'deprecated', 'internal') as $annotation) {
|
foreach (array('final', 'deprecated', 'internal') as $annotation) {
|
||||||
if (false !== strpos($doc, '@'.$annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
|
if (false !== \strpos($doc, $annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
|
||||||
self::${$annotation}[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
self::${$annotation}[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$parentAndTraits = class_uses($name, false);
|
$parentAndTraits = \class_uses($name, false);
|
||||||
if ($parent = get_parent_class($class)) {
|
if ($parent = \get_parent_class($class)) {
|
||||||
$parentAndTraits[] = $parent;
|
$parentAndTraits[] = $parent;
|
||||||
|
|
||||||
|
if (!isset(self::$checkedClasses[$parent])) {
|
||||||
|
$this->checkClass($parent);
|
||||||
|
}
|
||||||
|
|
||||||
if (isset(self::$final[$parent])) {
|
if (isset(self::$final[$parent])) {
|
||||||
@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);
|
@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);
|
||||||
}
|
}
|
||||||
@ -192,13 +216,16 @@ class DebugClassLoader
|
|||||||
|
|
||||||
// Detect if the parent is annotated
|
// Detect if the parent is annotated
|
||||||
foreach ($parentAndTraits + $this->getOwnInterfaces($name, $parent) as $use) {
|
foreach ($parentAndTraits + $this->getOwnInterfaces($name, $parent) as $use) {
|
||||||
if (isset(self::$deprecated[$use]) && strncmp($ns, $use, $len)) {
|
if (!isset(self::$checkedClasses[$use])) {
|
||||||
|
$this->checkClass($use);
|
||||||
|
}
|
||||||
|
if (isset(self::$deprecated[$use]) && \strncmp($ns, $use, $len)) {
|
||||||
$type = class_exists($name, false) ? 'class' : (interface_exists($name, false) ? 'interface' : 'trait');
|
$type = class_exists($name, false) ? 'class' : (interface_exists($name, false) ? 'interface' : 'trait');
|
||||||
$verb = class_exists($use, false) || interface_exists($name, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
|
$verb = class_exists($use, false) || interface_exists($name, false) ? 'extends' : (interface_exists($use, false) ? 'implements' : 'uses');
|
||||||
|
|
||||||
@trigger_error(sprintf('The "%s" %s %s "%s" that is deprecated%s.', $name, $type, $verb, $use, self::$deprecated[$use]), E_USER_DEPRECATED);
|
@trigger_error(sprintf('The "%s" %s %s "%s" that is deprecated%s.', $name, $type, $verb, $use, self::$deprecated[$use]), E_USER_DEPRECATED);
|
||||||
}
|
}
|
||||||
if (isset(self::$internal[$use]) && strncmp($ns, $use, $len)) {
|
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
|
||||||
@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);
|
@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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,12 +236,12 @@ class DebugClassLoader
|
|||||||
foreach ($parentAndTraits as $use) {
|
foreach ($parentAndTraits as $use) {
|
||||||
foreach (array('finalMethods', 'internalMethods') as $property) {
|
foreach (array('finalMethods', 'internalMethods') as $property) {
|
||||||
if (isset(self::${$property}[$use])) {
|
if (isset(self::${$property}[$use])) {
|
||||||
self::${$property}[$name] = array_merge(self::${$property}[$name], self::${$property}[$use]);
|
self::${$property}[$name] = self::${$property}[$name] ? self::${$property}[$use] + self::${$property}[$name] : self::${$property}[$use];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$isClass = class_exists($name, false);
|
$isClass = \class_exists($name, false);
|
||||||
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
|
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) {
|
||||||
if ($method->class !== $name) {
|
if ($method->class !== $name) {
|
||||||
continue;
|
continue;
|
||||||
@ -233,7 +260,7 @@ class DebugClassLoader
|
|||||||
foreach ($parentAndTraits as $use) {
|
foreach ($parentAndTraits as $use) {
|
||||||
if (isset(self::$internalMethods[$use][$method->name])) {
|
if (isset(self::$internalMethods[$use][$method->name])) {
|
||||||
list($declaringClass, $message) = self::$internalMethods[$use][$method->name];
|
list($declaringClass, $message) = self::$internalMethods[$use][$method->name];
|
||||||
if (strncmp($ns, $declaringClass, $len)) {
|
if (\strncmp($ns, $declaringClass, $len)) {
|
||||||
@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);
|
@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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,14 +272,14 @@ class DebugClassLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (array('final', 'internal') as $annotation) {
|
foreach (array('final', 'internal') as $annotation) {
|
||||||
if (false !== strpos($doc, '@'.$annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
|
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
|
||||||
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
||||||
self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
|
self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) {
|
if (isset(self::$php7Reserved[\strtolower($refl->getShortName())])) {
|
||||||
@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);
|
@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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -350,8 +377,6 @@ class DebugClassLoader
|
|||||||
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)));
|
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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
--TEST--
|
||||||
|
Test DebugClassLoader with previoulsy loaded parents
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
|
use Symfony\Component\Debug\DebugClassLoader;
|
||||||
|
|
||||||
|
$vendor = __DIR__;
|
||||||
|
while (!file_exists($vendor.'/vendor')) {
|
||||||
|
$vendor = dirname($vendor);
|
||||||
|
}
|
||||||
|
require $vendor.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
class_exists(FinalMethod::class);
|
||||||
|
|
||||||
|
set_error_handler(function ($type, $msg) { echo $msg, "\n"; });
|
||||||
|
|
||||||
|
DebugClassLoader::enable();
|
||||||
|
|
||||||
|
class_exists(ExtendedFinalMethod::class);
|
||||||
|
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
The "Symfony\Component\Debug\Tests\Fixtures\FinalMethod::finalMethod()" method is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Symfony\Component\Debug\Tests\Fixtures\ExtendedFinalMethod".
|
Reference in New Issue
Block a user