[Debug] Detect virtual methods using @method
This commit is contained in:
parent
0acf9e17d4
commit
38877c32ac
@ -39,6 +39,7 @@ class DebugClassLoader
|
|||||||
private static $internalMethods = array();
|
private static $internalMethods = array();
|
||||||
private static $annotatedParameters = array();
|
private static $annotatedParameters = array();
|
||||||
private static $darwinCache = array('/' => array('/', array()));
|
private static $darwinCache = array('/' => array('/', array()));
|
||||||
|
private static $method = array();
|
||||||
|
|
||||||
public function __construct(callable $classLoader)
|
public function __construct(callable $classLoader)
|
||||||
{
|
{
|
||||||
@ -228,6 +229,24 @@ class DebugClassLoader
|
|||||||
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
foreach ($notice as $method) {
|
||||||
|
$static = '' !== $method[1];
|
||||||
|
$name = $method[2];
|
||||||
|
$description = $method[3] ?? null;
|
||||||
|
if (false === strpos($name, '(')) {
|
||||||
|
$name .= '()';
|
||||||
|
}
|
||||||
|
if (null !== $description) {
|
||||||
|
$description = trim($description);
|
||||||
|
if (!isset($method[4])) {
|
||||||
|
$description .= '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self::$method[$class][] = array($class, $name, $static, $description);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$parent = \get_parent_class($class);
|
$parent = \get_parent_class($class);
|
||||||
@ -258,6 +277,28 @@ class DebugClassLoader
|
|||||||
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
|
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
|
||||||
$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);
|
$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);
|
||||||
}
|
}
|
||||||
|
if (isset(self::$method[$use])) {
|
||||||
|
if ($refl->isAbstract()) {
|
||||||
|
if (isset(self::$method[$class])) {
|
||||||
|
self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]);
|
||||||
|
} else {
|
||||||
|
self::$method[$class] = self::$method[$use];
|
||||||
|
}
|
||||||
|
} elseif (!$refl->isInterface()) {
|
||||||
|
$hasCall = $refl->hasMethod('__call');
|
||||||
|
$hasStaticCall = $refl->hasMethod('__callStatic');
|
||||||
|
foreach (self::$method[$use] as $method) {
|
||||||
|
list($interface, $name, $static, $description) = $method;
|
||||||
|
if ($static ? $hasStaticCall : $hasCall) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$realName = substr($name, 0, strpos($name, '('));
|
||||||
|
if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) {
|
||||||
|
$deprecations[] = sprintf('Class "%s" should implement method "%s::%s"%s', $class, ($static ? 'static ' : '').$interface, $name, null == $description ? '.' : ': '.$description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\trait_exists($class)) {
|
if (\trait_exists($class)) {
|
||||||
|
@ -304,6 +304,46 @@ class DebugClassLoaderTest extends TestCase
|
|||||||
|
|
||||||
$this->assertSame(array(), $deprecations);
|
$this->assertSame(array(), $deprecations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testVirtualUse()
|
||||||
|
{
|
||||||
|
$deprecations = array();
|
||||||
|
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
||||||
|
$e = error_reporting(E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtual', true);
|
||||||
|
|
||||||
|
error_reporting($e);
|
||||||
|
restore_error_handler();
|
||||||
|
|
||||||
|
$this->assertSame(array(
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::sameLineInterfaceMethodNoBraces()".',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::newLineInterfaceMethod()": Some description!',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::newLineInterfaceMethodNoBraces()": Description.',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::invalidInterfaceMethod()".',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::invalidInterfaceMethodNoBraces()".',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::complexInterfaceMethod($arg, ...$args)".',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::complexInterfaceMethodTyped($arg, int ...$args)": Description ...',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodNoBraces()".',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodTyped(int $arg)": Description.',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodTypedNoBraces()".',
|
||||||
|
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtual" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualSubInterface::subInterfaceMethod()".',
|
||||||
|
), $deprecations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVirtualUseWithMagicCall()
|
||||||
|
{
|
||||||
|
$deprecations = array();
|
||||||
|
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
|
||||||
|
$e = error_reporting(E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtualMagicCall', true);
|
||||||
|
|
||||||
|
error_reporting($e);
|
||||||
|
restore_error_handler();
|
||||||
|
|
||||||
|
$this->assertSame(array(), $deprecations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClassLoader
|
class ClassLoader
|
||||||
@ -359,6 +399,32 @@ class ClassLoader
|
|||||||
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
|
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
|
||||||
} elseif ('Test\\'.__NAMESPACE__.'\UseTraitWithInternalMethod' === $class) {
|
} elseif ('Test\\'.__NAMESPACE__.'\UseTraitWithInternalMethod' === $class) {
|
||||||
eval('namespace Test\\'.__NAMESPACE__.'; class UseTraitWithInternalMethod { use \\'.__NAMESPACE__.'\Fixtures\TraitWithInternalMethod; }');
|
eval('namespace Test\\'.__NAMESPACE__.'; class UseTraitWithInternalMethod { use \\'.__NAMESPACE__.'\Fixtures\TraitWithInternalMethod; }');
|
||||||
|
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtual' === $class) {
|
||||||
|
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtual extends ExtendsVirtualParent implements \\'.__NAMESPACE__.'\Fixtures\VirtualSubInterface {
|
||||||
|
public function ownClassMethod() { }
|
||||||
|
public function classMethod() { }
|
||||||
|
public function sameLineInterfaceMethodNoBraces() { }
|
||||||
|
}');
|
||||||
|
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualParent' === $class) {
|
||||||
|
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualParent extends ExtendsVirtualAbstract {
|
||||||
|
public function ownParentMethod() { }
|
||||||
|
public function traitMethod() { }
|
||||||
|
public function sameLineInterfaceMethod() { }
|
||||||
|
public function staticMethodNoBraces() { } // should be static
|
||||||
|
}');
|
||||||
|
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstract' === $class) {
|
||||||
|
eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstract extends ExtendsVirtualAbstractBase {
|
||||||
|
public static function staticMethod() { }
|
||||||
|
public function ownAbstractMethod() { }
|
||||||
|
public function interfaceMethod() { }
|
||||||
|
}');
|
||||||
|
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstractBase' === $class) {
|
||||||
|
eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstractBase extends \\'.__NAMESPACE__.'\Fixtures\VirtualClass implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
|
||||||
|
public function ownAbstractBaseMethod() { }
|
||||||
|
}');
|
||||||
|
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualMagicCall' === $class) {
|
||||||
|
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualMagicCall extends \\'.__NAMESPACE__.'\Fixtures\VirtualClassMagicCall implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
|
||||||
|
}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
src/Symfony/Component/Debug/Tests/Fixtures/VirtualClass.php
Normal file
11
src/Symfony/Component/Debug/Tests/Fixtures/VirtualClass.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method string classMethod()
|
||||||
|
*/
|
||||||
|
class VirtualClass
|
||||||
|
{
|
||||||
|
use VirtualTrait;
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method string magicMethod()
|
||||||
|
* @method static string staticMagicMethod()
|
||||||
|
*/
|
||||||
|
class VirtualClassMagicCall
|
||||||
|
{
|
||||||
|
public static function __callStatic($name, $arguments)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __call($name, $arguments)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method string interfaceMethod()
|
||||||
|
* @method sameLineInterfaceMethod($arg)
|
||||||
|
* @method sameLineInterfaceMethodNoBraces
|
||||||
|
*
|
||||||
|
* Ignored
|
||||||
|
* @method
|
||||||
|
* @method
|
||||||
|
*
|
||||||
|
* Not ignored
|
||||||
|
* @method newLineInterfaceMethod() Some description!
|
||||||
|
* @method \stdClass newLineInterfaceMethodNoBraces Description
|
||||||
|
*
|
||||||
|
* Invalid
|
||||||
|
* @method unknownType invalidInterfaceMethod()
|
||||||
|
* @method unknownType|string invalidInterfaceMethodNoBraces
|
||||||
|
*
|
||||||
|
* Complex
|
||||||
|
* @method complexInterfaceMethod($arg, ...$args)
|
||||||
|
* @method string[]|int complexInterfaceMethodTyped($arg, int ...$args) Description ...
|
||||||
|
*
|
||||||
|
* Static
|
||||||
|
* @method static Foo&Bar staticMethod()
|
||||||
|
* @method static staticMethodNoBraces
|
||||||
|
* @method static \stdClass staticMethodTyped(int $arg) Description
|
||||||
|
* @method static \stdClass[] staticMethodTypedNoBraces
|
||||||
|
*/
|
||||||
|
interface VirtualInterface
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method string subInterfaceMethod()
|
||||||
|
*/
|
||||||
|
interface VirtualSubInterface extends VirtualInterface
|
||||||
|
{
|
||||||
|
}
|
10
src/Symfony/Component/Debug/Tests/Fixtures/VirtualTrait.php
Normal file
10
src/Symfony/Component/Debug/Tests/Fixtures/VirtualTrait.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Debug\Tests\Fixtures;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method string traitMethod()
|
||||||
|
*/
|
||||||
|
trait VirtualTrait
|
||||||
|
{
|
||||||
|
}
|
Reference in New Issue
Block a user