Skip to content

Commit 92b917e

Browse files
[ErrorHandler] trigger deprecations for @final properties
1 parent 01d2d3e commit 92b917e

File tree

16 files changed

+106
-45
lines changed

16 files changed

+106
-45
lines changed

UPGRADE-6.1.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
UPGRADE FROM 6.0 to 6.1
22
=======================
33

4+
All components
5+
--------------
6+
7+
* Non-static public and protected properties are now considered final;
8+
instead of overriding a property, consider setting its value in the constructor
9+
410
Console
511
-------
612

7-
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead.
13+
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
814

915
Serializer
1016
----------

src/Symfony/Component/Cache/Tests/Adapter/MaxIdLengthAdapterTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,10 @@ public function testTooLongNamespace()
7979

8080
abstract class MaxIdLengthAdapter extends AbstractAdapter
8181
{
82-
protected $maxIdLength = 50;
83-
8482
public function __construct(string $ns)
8583
{
84+
$this->maxIdLength = 50;
85+
8686
parent::__construct($ns);
8787
}
8888
}

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ CHANGELOG
55
---
66

77
* Add method `__toString()` to `InputInterface`
8-
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead.
8+
* Deprecate `Command::$defaultName` and `Command::$defaultDescription`, use the `AsCommand` attribute instead
99

1010
6.0
1111
---

src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -413,16 +413,6 @@ class ProjectServiceContainer extends Container
413413
public $__foo_bar;
414414
public $__foo_baz;
415415
public $__internal;
416-
protected $privates;
417-
protected $methodMap = [
418-
'bar' => 'getBarService',
419-
'foo_bar' => 'getFooBarService',
420-
'foo.baz' => 'getFoo_BazService',
421-
'circular' => 'getCircularService',
422-
'throw_exception' => 'getThrowExceptionService',
423-
'throws_exception_on_service_configuration' => 'getThrowsExceptionOnServiceConfigurationService',
424-
'internal_dependency' => 'getInternalDependencyService',
425-
];
426416

427417
public function __construct()
428418
{
@@ -434,6 +424,15 @@ public function __construct()
434424
$this->__internal = new \stdClass();
435425
$this->privates = [];
436426
$this->aliases = ['alias' => 'bar'];
427+
$this->methodMap = [
428+
'bar' => 'getBarService',
429+
'foo_bar' => 'getFooBarService',
430+
'foo.baz' => 'getFoo_BazService',
431+
'circular' => 'getCircularService',
432+
'throw_exception' => 'getThrowExceptionService',
433+
'throws_exception_on_service_configuration' => 'getThrowsExceptionOnServiceConfigurationService',
434+
'internal_dependency' => 'getInternalDependencyService',
435+
];
437436
}
438437

439438
protected function getInternalService()

src/Symfony/Component/DependencyInjection/Tests/Loader/FileLoaderTest.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function testRegisterClasses()
8989
$container = new ContainerBuilder();
9090
$container->setParameter('sub_dir', 'Sub');
9191
$loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'));
92-
$loader->autoRegisterAliasesForSinglyImplementedInterfaces = false;
92+
$loader->noAutoRegisterAliasesForSinglyImplementedInterfaces();
9393

9494
$loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*');
9595
$loader->registerClasses(new Definition(), 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\\', 'Prototype/%sub_dir%/*'); // loading twice should not be an issue
@@ -270,7 +270,10 @@ public function testRegisterClassesWithWhenEnv(?string $env, bool $expected)
270270

271271
class TestFileLoader extends FileLoader
272272
{
273-
public $autoRegisterAliasesForSinglyImplementedInterfaces = true;
273+
public function noAutoRegisterAliasesForSinglyImplementedInterfaces()
274+
{
275+
$this->autoRegisterAliasesForSinglyImplementedInterfaces = false;
276+
}
274277

275278
public function load(mixed $resource, string $type = null): mixed
276279
{

src/Symfony/Component/ErrorHandler/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ CHANGELOG
44
6.1
55
---
66

7-
* Report overridden `@final` constants
7+
* Report overridden `@final` constants and properties
88

99
5.4
1010
---

src/Symfony/Component/ErrorHandler/DebugClassLoader.php

Lines changed: 22 additions & 13 deletions
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 $finalProperties = [];
115116
private static array $finalConstants = [];
116117
private static array $deprecated = [];
117118
private static array $internal = [];
@@ -469,9 +470,10 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
469470
self::$finalMethods[$class] = [];
470471
self::$internalMethods[$class] = [];
471472
self::$annotatedParameters[$class] = [];
473+
self::$finalProperties[$class] = [];
472474
self::$finalConstants[$class] = [];
473475
foreach ($parentAndOwnInterfaces as $use) {
474-
foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes', 'finalConstants'] as $property) {
476+
foreach (['finalMethods', 'internalMethods', 'annotatedParameters', 'returnTypes', 'finalProperties', 'finalConstants'] as $property) {
475477
if (isset(self::${$property}[$use])) {
476478
self::${$property}[$class] = self::${$property}[$class] ? self::${$property}[$use] + self::${$property}[$class] : self::${$property}[$use];
477479
}
@@ -626,22 +628,29 @@ public function checkAnnotations(\ReflectionClass $refl, string $class): array
626628
}
627629
}
628630

629-
foreach ($refl->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC | \ReflectionClassConstant::IS_PROTECTED) as $constant) {
630-
if ($constant->class !== $class) {
631-
continue;
632-
}
631+
$finals = isset(self::$final[$class]) || $refl->isFinal() ? [] : [
632+
'finalConstants' => $refl->getReflectionConstants(\ReflectionClassConstant::IS_PUBLIC | \ReflectionClassConstant::IS_PROTECTED),
633+
'finalProperties' => $refl->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED),
634+
];
635+
foreach ($finals as $type => $reflectors) {
636+
foreach ($reflectors as $r) {
637+
if ($r->class !== $class) {
638+
continue;
639+
}
633640

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);
641+
foreach ($parentAndOwnInterfaces as $use) {
642+
if (isset(self::${$type}[$use][$r->name]) && ('finalConstants' === $type || substr($use, 0, strrpos($use, '\\')) !== substr($use, 0, strrpos($class, '\\')))) {
643+
$msg = 'finalConstants' === $type ? '%s" constant' : '$%s" property';
644+
$deprecations[] = sprintf('The "%s::'.$msg.' is considered final. You should not override it in "%s".', self::${$type}[$use][$r->name], $r->name, $class);
645+
}
637646
}
638-
}
639647

640-
if (!($doc = $this->parsePhpDoc($constant)) || !isset($doc['final'])) {
641-
continue;
642-
}
648+
$doc = $this->parsePhpDoc($r);
643649

644-
self::$finalConstants[$class][$constant->name] = $class;
650+
if (isset($doc['final']) || ('finalProperties' === $type && str_starts_with($class, 'Symfony\\') && !$r->isStatic() && !$r->hasType())) {
651+
self::${$type}[$class][$r->name] = $class;
652+
}
653+
}
645654
}
646655

647656
return $deprecations;

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,13 +401,30 @@ class_exists('Test\\'.ReturnType::class, true);
401401
], $deprecations);
402402
}
403403

404+
public function testOverrideFinalProperty()
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\OverrideFinalProperty::class, true);
411+
412+
error_reporting($e);
413+
restore_error_handler();
414+
415+
$this->assertSame([
416+
'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalProperty\FinalProperty::$pub" property is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\OverrideFinalProperty".',
417+
'The "Symfony\Component\ErrorHandler\Tests\Fixtures\FinalProperty\FinalProperty::$prot" property is considered final. You should not override it in "Symfony\Component\ErrorHandler\Tests\Fixtures\OverrideFinalProperty".',
418+
], $deprecations);
419+
}
420+
404421
public function testOverrideFinalConstant()
405422
{
406423
$deprecations = [];
407424
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
408425
$e = error_reporting(E_USER_DEPRECATED);
409426

410-
class_exists( Fixtures\FinalConstant\OverrideFinalConstant::class, true);
427+
class_exists(Fixtures\FinalConstant\OverrideFinalConstant::class, true);
411428

412429
error_reporting($e);
413430
restore_error_handler();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures\FinalProperty;
4+
5+
class FinalProperty
6+
{
7+
/**
8+
* @final
9+
*/
10+
public $pub;
11+
12+
/**
13+
* @final
14+
*/
15+
protected $prot;
16+
17+
/**
18+
* @final
19+
*/
20+
private $priv;
21+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Symfony\Component\ErrorHandler\Tests\Fixtures;
4+
5+
use Symfony\Component\ErrorHandler\Tests\Fixtures\FinalProperty\FinalProperty;
6+
7+
class OverrideFinalProperty extends FinalProperty
8+
{
9+
public $pub;
10+
protected $prot;
11+
private $priv;
12+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy