Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 38877c3

Browse files
ro0NLfabpot
authored andcommitted
[Debug] Detect virtual methods using @method
1 parent 0acf9e1 commit 38877c3

File tree

7 files changed

+190
-0
lines changed

7 files changed

+190
-0
lines changed

src/Symfony/Component/Debug/DebugClassLoader.php

+41
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class DebugClassLoader
3939
private static $internalMethods = array();
4040
private static $annotatedParameters = array();
4141
private static $darwinCache = array('/' => array('/', array()));
42+
private static $method = array();
4243

4344
public function __construct(callable $classLoader)
4445
{
@@ -228,6 +229,24 @@ public function checkAnnotations(\ReflectionClass $refl, $class)
228229
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
229230
}
230231
}
232+
233+
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)) {
234+
foreach ($notice as $method) {
235+
$static = '' !== $method[1];
236+
$name = $method[2];
237+
$description = $method[3] ?? null;
238+
if (false === strpos($name, '(')) {
239+
$name .= '()';
240+
}
241+
if (null !== $description) {
242+
$description = trim($description);
243+
if (!isset($method[4])) {
244+
$description .= '.';
245+
}
246+
}
247+
self::$method[$class][] = array($class, $name, $static, $description);
248+
}
249+
}
231250
}
232251

233252
$parent = \get_parent_class($class);
@@ -258,6 +277,28 @@ public function checkAnnotations(\ReflectionClass $refl, $class)
258277
if (isset(self::$internal[$use]) && \strncmp($ns, $use, $len)) {
259278
$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);
260279
}
280+
if (isset(self::$method[$use])) {
281+
if ($refl->isAbstract()) {
282+
if (isset(self::$method[$class])) {
283+
self::$method[$class] = array_merge(self::$method[$class], self::$method[$use]);
284+
} else {
285+
self::$method[$class] = self::$method[$use];
286+
}
287+
} elseif (!$refl->isInterface()) {
288+
$hasCall = $refl->hasMethod('__call');
289+
$hasStaticCall = $refl->hasMethod('__callStatic');
290+
foreach (self::$method[$use] as $method) {
291+
list($interface, $name, $static, $description) = $method;
292+
if ($static ? $hasStaticCall : $hasCall) {
293+
continue;
294+
}
295+
$realName = substr($name, 0, strpos($name, '('));
296+
if (!$refl->hasMethod($realName) || !($methodRefl = $refl->getMethod($realName))->isPublic() || ($static && !$methodRefl->isStatic()) || (!$static && $methodRefl->isStatic())) {
297+
$deprecations[] = sprintf('Class "%s" should implement method "%s::%s"%s', $class, ($static ? 'static ' : '').$interface, $name, null == $description ? '.' : ': '.$description);
298+
}
299+
}
300+
}
301+
}
261302
}
262303

263304
if (\trait_exists($class)) {

src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php

+66
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,46 @@ class_exists('Test\\'.__NAMESPACE__.'\\UseTraitWithInternalMethod', true);
304304

305305
$this->assertSame(array(), $deprecations);
306306
}
307+
308+
public function testVirtualUse()
309+
{
310+
$deprecations = array();
311+
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
312+
$e = error_reporting(E_USER_DEPRECATED);
313+
314+
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtual', true);
315+
316+
error_reporting($e);
317+
restore_error_handler();
318+
319+
$this->assertSame(array(
320+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::sameLineInterfaceMethodNoBraces()".',
321+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::newLineInterfaceMethod()": Some description!',
322+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::newLineInterfaceMethodNoBraces()": Description.',
323+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::invalidInterfaceMethod()".',
324+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::invalidInterfaceMethodNoBraces()".',
325+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::complexInterfaceMethod($arg, ...$args)".',
326+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::complexInterfaceMethodTyped($arg, int ...$args)": Description ...',
327+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodNoBraces()".',
328+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodTyped(int $arg)": Description.',
329+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtualParent" should implement method "static Symfony\Component\Debug\Tests\Fixtures\VirtualInterface::staticMethodTypedNoBraces()".',
330+
'Class "Test\Symfony\Component\Debug\Tests\ExtendsVirtual" should implement method "Symfony\Component\Debug\Tests\Fixtures\VirtualSubInterface::subInterfaceMethod()".',
331+
), $deprecations);
332+
}
333+
334+
public function testVirtualUseWithMagicCall()
335+
{
336+
$deprecations = array();
337+
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
338+
$e = error_reporting(E_USER_DEPRECATED);
339+
340+
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsVirtualMagicCall', true);
341+
342+
error_reporting($e);
343+
restore_error_handler();
344+
345+
$this->assertSame(array(), $deprecations);
346+
}
307347
}
308348

309349
class ClassLoader
@@ -359,6 +399,32 @@ public function internalMethod() { }
359399
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
360400
} elseif ('Test\\'.__NAMESPACE__.'\UseTraitWithInternalMethod' === $class) {
361401
eval('namespace Test\\'.__NAMESPACE__.'; class UseTraitWithInternalMethod { use \\'.__NAMESPACE__.'\Fixtures\TraitWithInternalMethod; }');
402+
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtual' === $class) {
403+
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtual extends ExtendsVirtualParent implements \\'.__NAMESPACE__.'\Fixtures\VirtualSubInterface {
404+
public function ownClassMethod() { }
405+
public function classMethod() { }
406+
public function sameLineInterfaceMethodNoBraces() { }
407+
}');
408+
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualParent' === $class) {
409+
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualParent extends ExtendsVirtualAbstract {
410+
public function ownParentMethod() { }
411+
public function traitMethod() { }
412+
public function sameLineInterfaceMethod() { }
413+
public function staticMethodNoBraces() { } // should be static
414+
}');
415+
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstract' === $class) {
416+
eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstract extends ExtendsVirtualAbstractBase {
417+
public static function staticMethod() { }
418+
public function ownAbstractMethod() { }
419+
public function interfaceMethod() { }
420+
}');
421+
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualAbstractBase' === $class) {
422+
eval('namespace Test\\'.__NAMESPACE__.'; abstract class ExtendsVirtualAbstractBase extends \\'.__NAMESPACE__.'\Fixtures\VirtualClass implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
423+
public function ownAbstractBaseMethod() { }
424+
}');
425+
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsVirtualMagicCall' === $class) {
426+
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsVirtualMagicCall extends \\'.__NAMESPACE__.'\Fixtures\VirtualClassMagicCall implements \\'.__NAMESPACE__.'\Fixtures\VirtualInterface {
427+
}');
362428
}
363429
}
364430
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @method string classMethod()
7+
*/
8+
class VirtualClass
9+
{
10+
use VirtualTrait;
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @method string magicMethod()
7+
* @method static string staticMagicMethod()
8+
*/
9+
class VirtualClassMagicCall
10+
{
11+
public static function __callStatic($name, $arguments)
12+
{
13+
}
14+
15+
public function __call($name, $arguments)
16+
{
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @method string interfaceMethod()
7+
* @method sameLineInterfaceMethod($arg)
8+
* @method sameLineInterfaceMethodNoBraces
9+
*
10+
* Ignored
11+
* @method
12+
* @method
13+
*
14+
* Not ignored
15+
* @method newLineInterfaceMethod() Some description!
16+
* @method \stdClass newLineInterfaceMethodNoBraces Description
17+
*
18+
* Invalid
19+
* @method unknownType invalidInterfaceMethod()
20+
* @method unknownType|string invalidInterfaceMethodNoBraces
21+
*
22+
* Complex
23+
* @method complexInterfaceMethod($arg, ...$args)
24+
* @method string[]|int complexInterfaceMethodTyped($arg, int ...$args) Description ...
25+
*
26+
* Static
27+
* @method static Foo&Bar staticMethod()
28+
* @method static staticMethodNoBraces
29+
* @method static \stdClass staticMethodTyped(int $arg) Description
30+
* @method static \stdClass[] staticMethodTypedNoBraces
31+
*/
32+
interface VirtualInterface
33+
{
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @method string subInterfaceMethod()
7+
*/
8+
interface VirtualSubInterface extends VirtualInterface
9+
{
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
/**
6+
* @method string traitMethod()
7+
*/
8+
trait VirtualTrait
9+
{
10+
}

0 commit comments

Comments
 (0)