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 38877c3

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

File tree

Expand file treeCollapse file tree

7 files changed

+190
-0
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+190
-0
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/DebugClassLoader.php
+41Lines changed: 41 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php
+66Lines changed: 66 additions & 0 deletions
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
}
+11Lines changed: 11 additions & 0 deletions
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+
}
+18Lines changed: 18 additions & 0 deletions
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+
}
+34Lines changed: 34 additions & 0 deletions
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+
}
+10Lines changed: 10 additions & 0 deletions
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+
}
+10Lines changed: 10 additions & 0 deletions
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.