-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Debug] Detect internal and deprecated methods #23816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,10 +27,12 @@ class DebugClassLoader | |
private $classLoader; | ||
private $isFinder; | ||
private static $caseCheck; | ||
private static $internal = array(); | ||
private static $final = array(); | ||
private static $finalMethods = array(); | ||
private static $deprecated = array(); | ||
private static $deprecatedMethods = array(); | ||
private static $internal = array(); | ||
private static $internalMethods = array(); | ||
private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null'); | ||
private static $darwinCache = array('/' => array('/', array())); | ||
|
||
|
@@ -166,53 +168,6 @@ public function loadClass($class) | |
throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: "%s" vs "%s".', $class, $name)); | ||
} | ||
|
||
$parent = get_parent_class($class); | ||
$doc = $refl->getDocComment(); | ||
if (preg_match('#\n \* @internal(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) { | ||
self::$internal[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; | ||
} | ||
|
||
// Not an interface nor a trait | ||
if (class_exists($name, false)) { | ||
if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) { | ||
self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; | ||
} | ||
|
||
if ($parent && 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); | ||
} | ||
|
||
// Inherit @final annotations | ||
self::$finalMethods[$name] = $parent && isset(self::$finalMethods[$parent]) ? self::$finalMethods[$parent] : array(); | ||
|
||
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { | ||
if ($method->class !== $name) { | ||
continue; | ||
} | ||
|
||
if ($parent && isset(self::$finalMethods[$parent][$method->name])) { | ||
@trigger_error(sprintf('%s It may change without further notice as of its next major version. You should not extend it from "%s".', self::$finalMethods[$parent][$method->name], $name), E_USER_DEPRECATED); | ||
} | ||
|
||
$doc = $method->getDocComment(); | ||
if (false === $doc || false === strpos($doc, '@final')) { | ||
continue; | ||
} | ||
|
||
if (preg_match('#\n\s+\* @final(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) { | ||
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; | ||
self::$finalMethods[$name][$method->name] = sprintf('The "%s::%s()" method is considered final%s.', $name, $method->name, $message); | ||
} | ||
} | ||
} | ||
|
||
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { | ||
@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); | ||
} | ||
if (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { | ||
self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); | ||
} | ||
|
||
// Don't trigger deprecations for classes in the same vendor | ||
if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) { | ||
$len = 0; | ||
|
@@ -228,40 +183,88 @@ public function loadClass($class) | |
} | ||
} | ||
|
||
foreach (array_merge(array($parent), class_implements($name, false), class_uses($name, false)) as $use) { | ||
// Detect annotations on the class | ||
if (false !== $doc = $refl->getDocComment()) { | ||
foreach (array('final', 'deprecated', 'internal') as $annotation) { | ||
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]) : ''; | ||
} | ||
} | ||
} | ||
|
||
if ($parent = get_parent_class($class)) { | ||
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); | ||
} | ||
} | ||
|
||
$traits = class_uses($name, false); | ||
$interfaces = $this->getOwnInterfaces($name); | ||
|
||
// Detect if the parent is annotated | ||
foreach (array_merge(array($parent), $interfaces, $traits) as $use) { | ||
if (isset(self::$deprecated[$use]) && strncmp($ns, $use, $len)) { | ||
$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'); | ||
|
||
@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)) { | ||
@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); | ||
} | ||
} | ||
|
||
if (!$parent || strncmp($ns, $parent, $len)) { | ||
if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { | ||
@trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); | ||
// Inherit @final and @deprecated annotations for methods | ||
self::$finalMethods[$name] = array(); | ||
self::$deprecatedMethods[$name] = array(); | ||
self::$internalMethods[$name] = array(); | ||
foreach (array_merge(array($parent), $traits) as $use) { | ||
foreach (array('finalMethods', 'deprecatedMethods', 'internalMethods') as $property) { | ||
if (isset(self::${$property}[$use])) { | ||
self::${$property}[$name] = array_merge(self::${$property}[$name], self::${$property}[$use]); | ||
} | ||
} | ||
} | ||
|
||
$isClass = class_exists($name, false); | ||
foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $method) { | ||
if ($method->class !== $name) { | ||
continue; | ||
} | ||
|
||
$parentInterfaces = array(); | ||
$deprecatedInterfaces = array(); | ||
if ($parent) { | ||
foreach (class_implements($parent) as $interface) { | ||
$parentInterfaces[$interface] = 1; | ||
} | ||
if ($isClass && $parent && isset(self::$finalMethods[$parent][$method->name])) { | ||
list($methodShortName, $message) = self::$finalMethods[$parent][$method->name]; | ||
@trigger_error(sprintf('The "%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".', $methodShortName, $message, $name), E_USER_DEPRECATED); | ||
} | ||
|
||
foreach ($refl->getInterfaceNames() as $interface) { | ||
if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { | ||
$deprecatedInterfaces[] = $interface; | ||
foreach (array_merge(array($parent), $traits) as $use) { | ||
if (isset(self::$deprecatedMethods[$use][$method->name]) && strncmp($ns, $use, $len)) { | ||
list($methodShortName, $message) = self::$deprecatedMethods[$use][$method->name]; | ||
@trigger_error(sprintf('The "%s" method is deprecated%s. You should not extend it from "%s".', $methodShortName, $message, $name), E_USER_DEPRECATED); | ||
} | ||
foreach (class_implements($interface) as $interface) { | ||
$parentInterfaces[$interface] = 1; | ||
if (isset(self::$internalMethods[$use][$method->name]) && strncmp($ns, $use, $len)) { | ||
list($methodShortName, $message) = self::$internalMethods[$use][$method->name]; | ||
@trigger_error(sprintf('The "%s" method is considered internal%s. It may change without further notice. You should not use it from "%s".', $methodShortName, $message, $name), E_USER_DEPRECATED); | ||
} | ||
} | ||
|
||
foreach ($deprecatedInterfaces as $interface) { | ||
if (!isset($parentInterfaces[$interface])) { | ||
@trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); | ||
// Detect method annotations | ||
$doc = $method->getDocComment(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could be merged with next line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
if (false === $doc) { | ||
continue; | ||
} | ||
|
||
foreach (array('final', 'deprecated', 'internal') as $annotation) { | ||
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]) : ''; | ||
self::${$annotation.'Methods'}[$name][$method->name] = array(sprintf('%s::%s()', $name, $method->name), $message); | ||
} | ||
} | ||
} | ||
|
||
if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { | ||
@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); | ||
} | ||
} | ||
|
||
if ($file) { | ||
|
@@ -361,4 +364,30 @@ public function loadClass($class) | |
return true; | ||
} | ||
} | ||
|
||
/** | ||
* `class_implements` includes interfaces from the parents so we have to | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can configure your IDE to auto-break lines at 120, not 80 :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done :) |
||
* manually exclude them. | ||
* | ||
* @param string $class | ||
* | ||
* @return string[] | ||
*/ | ||
private function getOwnInterfaces($class) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. $ownInterfaces = class_implements($class, false);
if ($parent = get_parent_class($class)) {
foreach (class_implements($parent, false) as $interface) {
unset($ownInterfaces[$interface]);
}
}
foreach ($ownInterfaces as $interface) {
foreach (class_implements($interface) as $interface) {
unset($ownInterfaces[$interface]);
}
}
return $ownInterfaces; |
||
{ | ||
$parentInterfaces = array(); | ||
if ($parent = get_parent_class($class)) { | ||
foreach (class_implements($parent, false) as $interface) { | ||
$parentInterfaces[$interface] = true; | ||
} | ||
} | ||
|
||
foreach (class_implements($class, false) as $interface) { | ||
foreach (class_implements($interface) as $interface) { | ||
$parentInterfaces[$interface] = true; | ||
} | ||
} | ||
|
||
return array_keys(array_diff_key(class_implements($class, false), $parentInterfaces)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
namespace Symfony\Component\Debug\Tests\Fixtures; | ||
|
||
class AnnotatedClass | ||
{ | ||
/** | ||
* @deprecated since version 3.4. | ||
*/ | ||
public function deprecatedMethod() | ||
{ | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,4 +8,8 @@ | |
class InternalClass | ||
{ | ||
use InternalTrait2; | ||
|
||
public function usedInInternalClass() | ||
{ | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$traits = array($parent) + class_uses($name, false);
Then below
foreach ($interfaces + $traits
to remove the array_merge() (directly$traits
after, maybe renamed btw)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
array($parent) + $interfaces;
could cause conflicts so I did$parentAndTraits = ($parent ? array($parent => $parent) : array()) + class_uses($name, false);
instead.