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

Skip to content

Commit c8fdf03

Browse files
GuilhemNnicolas-grekas
authored andcommitted
[Debug] Trigger a deprecation for new parameters not defined in sub classes
1 parent d4e1cd9 commit c8fdf03

File tree

3 files changed

+90
-9
lines changed

3 files changed

+90
-9
lines changed

src/Symfony/Component/Debug/DebugClassLoader.php

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\Debug;
1313

14+
use PHPUnit\Framework\MockObject\Matcher\StatelessInvocation;
15+
1416
/**
1517
* Autoloader checking if the class is really defined in the file found.
1618
*
@@ -34,6 +36,7 @@ class DebugClassLoader
3436
private static $deprecated = array();
3537
private static $internal = array();
3638
private static $internalMethods = array();
39+
private static $annotatedParameters = array();
3740
private static $darwinCache = array('/' => array('/', array()));
3841

3942
public function __construct(callable $classLoader)
@@ -229,11 +232,12 @@ private function checkClass($class, $file = null)
229232
}
230233
}
231234

232-
// Inherit @final and @internal annotations for methods
235+
// Inherit @final, @internal and @param annotations for methods
233236
self::$finalMethods[$name] = array();
234237
self::$internalMethods[$name] = array();
238+
self::$annotatedParameters[$name] = array();
235239
foreach ($parentAndTraits as $use) {
236-
foreach (array('finalMethods', 'internalMethods') as $property) {
240+
foreach (array('finalMethods', 'internalMethods', 'annotatedParameters') as $property) {
237241
if (isset(self::${$property}[$use])) {
238242
self::${$property}[$name] = self::${$property}[$name] ? self::${$property}[$use] + self::${$property}[$name] : self::${$property}[$use];
239243
}
@@ -258,20 +262,50 @@ private function checkClass($class, $file = null)
258262
}
259263
}
260264

261-
// Method from a trait
262-
if ($method->getFilename() !== $refl->getFileName()) {
263-
continue;
265+
if (isset(self::$annotatedParameters[$name][$method->name])) {
266+
$definedParameters = array();
267+
foreach ($method->getParameters() as $parameter) {
268+
$definedParameters[$parameter->name] = true;
269+
}
270+
271+
foreach (array_diff_key(self::$annotatedParameters[$name][$method->name], $definedParameters) as $deprecation) {
272+
@trigger_error(sprintf($deprecation, $name), E_USER_DEPRECATED);
273+
}
264274
}
265275

266276
// Detect method annotations
267277
if (false === $doc = $method->getDocComment()) {
268278
continue;
269279
}
270280

271-
foreach (array('final', 'internal') as $annotation) {
272-
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
273-
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
274-
self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
281+
$finalOrInternal = false;
282+
283+
// Skip methods from traits
284+
if ($method->getFilename() === $refl->getFileName()) {
285+
foreach (array('final', 'internal') as $annotation) {
286+
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
287+
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
288+
self::${$annotation.'Methods'}[$name][$method->name] = array($name, $message);
289+
$finalOrInternal = true;
290+
}
291+
}
292+
}
293+
294+
if ($finalOrInternal || $method->isConstructor() || false === \strpos($doc, '@param') || StatelessInvocation::class === $name) {
295+
continue;
296+
}
297+
298+
if (!preg_match_all('#\n\s+\* @param (.*?)(?<= )\$([a-zA-Z0-9_\x7f-\xff]++)#', $doc, $matches, PREG_SET_ORDER)) {
299+
continue;
300+
}
301+
$definedParameters = array();
302+
foreach ($method->getParameters() as $parameter) {
303+
$definedParameters[$parameter->name] = true;
304+
}
305+
foreach ($matches as list(, $parameterType, $parameterName)) {
306+
if (!isset($definedParameters[$parameterName])) {
307+
$parameterType = trim($parameterType);
308+
self::$annotatedParameters[$name][$method->name][$parameterName] = sprintf('The "%%s::%s()" method will require a new "%s$%s" argument in the next major version of its parent class "%s", not defining it is deprecated.', $method->name, $parameterType ? $parameterType.' ' : '', $parameterName, $method->class);
275309
}
276310
}
277311
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,20 @@ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true);
272272
'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait2::internalMethod()" method is considered internal. It may change without further notice. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".',
273273
));
274274
}
275+
276+
public function testExtendedMethodDefinesNewParameters()
277+
{
278+
$deprecations = array();
279+
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
280+
$e = error_reporting(E_USER_DEPRECATED);
281+
282+
class_exists('Test\\'.__NAMESPACE__.'\\SubClassWithAnnotatedParameters', true);
283+
284+
error_reporting($e);
285+
restore_error_handler();
286+
287+
$this->assertSame(array('The "Test\Symfony\Component\Debug\Tests\SubClassWithAnnotatedParameters::quzMethod()" method will require a new "Quz $quz" argument in the next major version of its parent class "Symfony\Component\Debug\Tests\Fixtures\ClassWithAnnotatedParameters", not defining it is deprecated.'), $deprecations);
288+
}
275289
}
276290

277291
class ClassLoader
@@ -325,6 +339,12 @@ public function internalMethod() { }
325339
}');
326340
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternalsParent' === $class) {
327341
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternalsParent extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { }');
342+
} elseif ('Test\\'.__NAMESPACE__.'\SubClassWithAnnotatedParameters' === $class) {
343+
eval('namespace Test\\'.__NAMESPACE__.'; class SubClassWithAnnotatedParameters extends \\'.__NAMESPACE__.'\Fixtures\ClassWithAnnotatedParameters {
344+
public function fooMethod(string $foo) { }
345+
public function barMethod($bar = null) { }
346+
public function quzMethod() { }
347+
}');
328348
}
329349
}
330350
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Symfony\Component\Debug\Tests\Fixtures;
4+
5+
class ClassWithAnnotatedParameters
6+
{
7+
/**
8+
* @param string $foo this is a foo parameter
9+
*/
10+
public function fooMethod(string $foo)
11+
{
12+
}
13+
14+
/**
15+
* @param string $bar parameter not implemented yet
16+
*/
17+
public function barMethod(/* string $bar = null */)
18+
{
19+
}
20+
21+
/**
22+
* @param Quz $quz parameter not implemented yet
23+
*/
24+
public function quzMethod(/* Quz $quz = null */)
25+
{
26+
}
27+
}

0 commit comments

Comments
 (0)