Skip to content

Commit 855fc71

Browse files
committed
[ErrorHandler] Report overridden @Final constants
1 parent ecd87db commit 855fc71

9 files changed

+180
-1
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
6.1
5+
---
6+
7+
* Report overridden `@final` constants
8+
49
5.4
510
---
611

DebugClassLoader.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class DebugClassLoader
112112
private static array $checkedClasses = [];
113113
private static array $final = [];
114114
private static array $finalMethods = [];
115+
private static array $finalConstants = [];
115116
private static array $deprecated = [];
116117
private static array $internal = [];
117118
private static array $internalMethods = [];
@@ -468,8 +469,9 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
468469
self::$finalMethods[$class] = [];
469470
self::$internalMethods[$class] = [];
470471
self::$annotatedParameters[$class] = [];
472+
self::$finalConstants[$class] = [];
471473
foreach ($parentAndOwnInterfaces as $use) {
472-
foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes'] as $property) {
474+
foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes', 'finalConstants'] as $property) {
473475
if (isset(self::${$property}[$use])) {
474476
self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
475477
}
@@ -624,6 +626,24 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
624626
}
625627
}
626628

629+
foreach ($refl->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC | \ReflectionClassConstant::IS_PROTECTED) as $constant) {
630+
if ($constant->class !== $class) {
631+
continue;
632+
}
633+
634+
foreach ($parentAndOwnInterfaces as $use) {
635+
if (isset(self::$finalConstants[$use][$constant->name])) {
636+
$deprecations[] = sprintf('The "%s::%s" constant is considered final. You should not override it in "%s".', self::$finalConstants[$use][$constant->name], $constant->name, $class);
637+
}
638+
}
639+
640+
if (!($doc = $this->parsePhpDoc($constant)) || !isset($doc['final'])) {
641+
continue;
642+
}
643+
644+
self::$finalConstants[$class][$constant->name] = $class;
645+
}
646+
627647
return $deprecations;
628648
}
629649

Tests/DebugClassLoaderTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,43 @@ class_exists('Test\\'.ReturnType::class, true);
400400
'Method "Symfony\Component\ErrorHandler\Tests\Fixtures\ReturnTypeParent::static()" might add "static" as a native return type declaration in the future. Do the same in child class "Test\Symfony\Component\ErrorHandler\Tests\ReturnType" now to avoid errors or add an explicit @return annotation to suppress this message.',
401401
], $deprecations);
402402
}
403+
404+
public function testOverrideFinalConstant()
405+
{
406+
$deprecations = [];
407+
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
408+
$e = error_reporting(E_USER_DEPRECATED);
409+
410+
class_exists( Fixtures\FinalConstant\OverrideFinalConstant::class, true);
411+
412+
error_reporting($e);
413+
restore_error_handler();
414+
415+
$this->assertSame([
416+
'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\FinalConstants::OVERRIDDEN_FINAL_PARENT_CLASS" constant is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\OverrideFinalConstant".',
417+
'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\FinalConstants2::OVERRIDDEN_FINAL_PARENT_PARENT_CLASS" constant is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\OverrideFinalConstant".',
418+
], $deprecations);
419+
}
420+
421+
/**
422+
* @requires PHP 8.1
423+
*/
424+
public function testOverrideFinalConstant81()
425+
{
426+
$deprecations = [];
427+
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
428+
$e = error_reporting(E_USER_DEPRECATED);
429+
430+
class_exists( Fixtures\FinalConstant\OverrideFinalConstant81::class, true);
431+
432+
error_reporting($e);
433+
restore_error_handler();
434+
435+
$this->assertSame([
436+
'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\FinalConstantsInterface::OVERRIDDEN_FINAL_INTERFACE" constant is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\OverrideFinalConstant81".',
437+
'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\FinalConstantsInterface2::OVERRIDDEN_FINAL_INTERFACE_2" constant is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant\OverrideFinalConstant81".',
438+
], $deprecations);
439+
}
403440
}
404441

405442
class ClassLoader
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant;
4+
5+
class FinalConstants extends FinalConstants2
6+
{
7+
/**
8+
* @final
9+
*/
10+
protected const OVERRIDDEN_FINAL_PARENT_CLASS = 'OVERRIDDEN_FINAL_PARENT_CLASS';
11+
12+
protected const OVERRIDDEN_NOT_FINAL_PARENT_CLASS = 'OVERRIDDEN_NOT_FINAL_PARENT_CLASS';
13+
14+
/**
15+
* @final
16+
*/
17+
public const NOT_OVERRIDDEN_FINAL_PARENT_CLASS = 'NOT_OVERRIDDEN_FINAL_PARENT_CLASS';
18+
19+
public const NOT_OVERRIDDEN_NOT_FINAL_PARENT_CLASS = 'NOT_OVERRIDDEN_NOT_FINAL_PARENT_CLASS';
20+
21+
private const FOO = 'FOO';
22+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant;
4+
5+
class FinalConstants2 {
6+
/**
7+
* @final
8+
*/
9+
public const OVERRIDDEN_FINAL_PARENT_PARENT_CLASS = 'OVERRIDDEN_FINAL_PARENT_PARENT_CLASS';
10+
11+
public const OVERRIDDEN_NOT_FINAL_PARENT_PARENT_CLASS = 'OVERRIDDEN_NOT_FINAL_PARENT_PARENT_CLASS';
12+
13+
/**
14+
* @final
15+
*/
16+
public const NOT_OVERRIDDEN_FINAL_PARENT_PARENT_CLASS = 'NOT_OVERRIDDEN_FINAL_PARENT_PARENT_CLASS';
17+
18+
public const NOT_OVERRIDDEN_NOT_FINAL_PARENT_PARENT_CLASS = 'NOT_OVERRIDDEN_NOT_FINAL_PARENT_PARENT_CLASS';
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant;
4+
5+
interface FinalConstantsInterface
6+
{
7+
/**
8+
* @final
9+
*/
10+
public const OVERRIDDEN_FINAL_INTERFACE = 'OVERRIDDEN_FINAL_INTERFACE';
11+
12+
public const OVERRIDDEN_NOT_FINAL_INTERFACE = 'OVERRIDDEN_NOT_FINAL_INTERFACE';
13+
14+
/**
15+
* @final
16+
*/
17+
public const NOT_OVERRIDDEN_FINAL_INTERFACE = 'NOT_OVERRIDDEN_FINAL_INTERFACE';
18+
19+
public const NOT_OVERRIDDEN_NOT_FINAL_INTERFACE = 'NOT_OVERRIDDEN_NOT_FINAL_INTERFACE';
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant;
4+
5+
interface FinalConstantsInterface2 extends FinalConstantsInterface
6+
{
7+
/**
8+
* @final for whatever reason
9+
*/
10+
public const OVERRIDDEN_FINAL_INTERFACE_2 = 'OVERRIDDEN_FINAL_INTERFACE_2';
11+
12+
public const OVERRIDDEN_NOT_FINAL_INTERFACE_2 = 'OVERRIDDEN_NOT_FINAL_INTERFACE_2';
13+
14+
/**
15+
* @final
16+
*/
17+
public const NOT_OVERRIDDEN_FINAL_INTERFACE_2 = 'NOT_OVERRIDDEN_FINAL_INTERFACE_2';
18+
19+
public const NOT_OVERRIDDEN_NOT_FINAL_INTERFACE_2 = 'NOT_OVERRIDDEN_NOT_FINAL_INTERFACE_2';
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant;
4+
5+
class OverrideFinalConstant extends FinalConstants
6+
{
7+
public const FOO = 'FOO';
8+
9+
protected const OVERRIDDEN_FINAL_PARENT_CLASS = 'O_OVERRIDDEN_FINAL_PARENT_CLASS';
10+
11+
protected const OVERRIDDEN_NOT_FINAL_PARENT_CLASS = 'O_OVERRIDDEN_NOT_FINAL_PARENT_CLASS';
12+
13+
public const OVERRIDDEN_FINAL_PARENT_PARENT_CLASS = 'O_OVERRIDDEN_FINAL_PARENT_PARENT_CLASS';
14+
15+
public const OVERRIDDEN_NOT_FINAL_PARENT_PARENT_CLASS = 'O_OVERRIDDEN_NOT_FINAL_PARENT_PARENT_CLASS';
16+
17+
private const CCC = 'CCC';
18+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures\FinalConstant;
4+
5+
class OverrideFinalConstant81 implements FinalConstantsInterface2
6+
{
7+
public const FOO = 'FOO';
8+
9+
public const OVERRIDDEN_FINAL_INTERFACE = 'O_OVERRIDDEN_FINAL_INTERFACE';
10+
11+
public const OVERRIDDEN_NOT_FINAL_INTERFACE = 'O_OVERRIDDEN_NOT_FINAL_INTERFACE';
12+
13+
public const OVERRIDDEN_FINAL_INTERFACE_2 = 'O_OVERRIDDEN_FINAL_INTERFACE_2';
14+
15+
public const OVERRIDDEN_NOT_FINAL_INTERFACE_2 = 'O_OVERRIDDEN_NOT_FINAL_INTERFACE_2';
16+
17+
private const CCC = 'CCC';
18+
}

0 commit comments

Comments
 (0)