File tree Expand file tree Collapse file tree
tests/PHPStan/Rules/Methods Expand file tree Collapse file tree Original file line number Diff line number Diff line change 2929 - PHPStan\Rules\Functions\InnerFunctionRule
3030 - PHPStan\Rules\Functions\PrintfParametersRule
3131 - PHPStan\Rules\Methods\ExistingClassesInTypehintsRule
32+ - PHPStan\Rules\Methods\OverridingMethodRule
3233 - PHPStan\Rules\Properties\AccessPropertiesInAssignRule
3334 - PHPStan\Rules\Properties\AccessStaticPropertiesInAssignRule
3435 - PHPStan\Rules\Variables\ThisVariableRule
Original file line number Diff line number Diff line change @@ -7,6 +7,8 @@ class MethodPrototypeReflection implements ClassMemberReflection
77
88 private \PHPStan \Reflection \ClassReflection $ declaringClass ;
99
10+ private string $ name ;
11+
1012 private bool $ isStatic ;
1113
1214 private bool $ isPrivate ;
@@ -15,19 +17,30 @@ class MethodPrototypeReflection implements ClassMemberReflection
1517
1618 private bool $ isAbstract ;
1719
20+ private bool $ isFinal ;
21+
1822 public function __construct (
23+ string $ name ,
1924 ClassReflection $ declaringClass ,
2025 bool $ isStatic ,
2126 bool $ isPrivate ,
2227 bool $ isPublic ,
23- bool $ isAbstract
28+ bool $ isAbstract ,
29+ bool $ isFinal
2430 )
2531 {
32+ $ this ->name = $ name ;
2633 $ this ->declaringClass = $ declaringClass ;
2734 $ this ->isStatic = $ isStatic ;
2835 $ this ->isPrivate = $ isPrivate ;
2936 $ this ->isPublic = $ isPublic ;
3037 $ this ->isAbstract = $ isAbstract ;
38+ $ this ->isFinal = $ isFinal ;
39+ }
40+
41+ public function getName (): string
42+ {
43+ return $ this ->name ;
3144 }
3245
3346 public function getDeclaringClass (): ClassReflection
@@ -55,6 +68,11 @@ public function isAbstract(): bool
5568 return $ this ->isAbstract ;
5669 }
5770
71+ public function isFinal (): bool
72+ {
73+ return $ this ->isFinal ;
74+ }
75+
5876 public function getDocComment (): ?string
5977 {
6078 return null ;
Original file line number Diff line number Diff line change @@ -84,11 +84,13 @@ public function getPrototype(): ClassMemberReflection
8484 $ prototypeDeclaringClass = $ this ->reflectionProvider ->getClass ($ prototypeMethod ->getDeclaringClass ()->getName ());
8585
8686 return new MethodPrototypeReflection (
87+ $ prototypeMethod ->getName (),
8788 $ prototypeDeclaringClass ,
8889 $ prototypeMethod ->isStatic (),
8990 $ prototypeMethod ->isPrivate (),
9091 $ prototypeMethod ->isPublic (),
91- $ prototypeMethod ->isAbstract ()
92+ $ prototypeMethod ->isAbstract (),
93+ $ prototypeMethod ->isFinal ()
9294 );
9395 } catch (\ReflectionException $ e ) {
9496 return $ this ;
@@ -125,7 +127,7 @@ public function isInternal(): TrinaryLogic
125127
126128 public function isFinal (): TrinaryLogic
127129 {
128- return TrinaryLogic::createNo ( );
130+ return TrinaryLogic::createFromBoolean ( $ this -> reflection -> isFinal () );
129131 }
130132
131133 public function getThrowType (): ?Type
Original file line number Diff line number Diff line change @@ -210,7 +210,11 @@ public function isInternal(): TrinaryLogic
210210
211211 public function isFinal (): TrinaryLogic
212212 {
213- return TrinaryLogic::createFromBoolean ($ this ->isFinal );
213+ $ finalMethod = false ;
214+ if ($ this ->functionLike instanceof ClassMethod) {
215+ $ finalMethod = $ this ->functionLike ->isFinal ();
216+ }
217+ return TrinaryLogic::createFromBoolean ($ finalMethod || $ this ->isFinal );
214218 }
215219
216220 public function getThrowType (): ?Type
Original file line number Diff line number Diff line change @@ -158,11 +158,13 @@ public function getPrototype(): ClassMemberReflection
158158 $ prototypeDeclaringClass = $ this ->reflectionProvider ->getClass ($ prototypeMethod ->getDeclaringClass ()->getName ());
159159
160160 return new MethodPrototypeReflection (
161+ $ prototypeMethod ->getName (),
161162 $ prototypeDeclaringClass ,
162163 $ prototypeMethod ->isStatic (),
163164 $ prototypeMethod ->isPrivate (),
164165 $ prototypeMethod ->isPublic (),
165- $ prototypeMethod ->isAbstract ()
166+ $ prototypeMethod ->isAbstract (),
167+ $ prototypeMethod ->isFinal ()
166168 );
167169 } catch (\ReflectionException $ e ) {
168170 return $ this ;
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace PHPStan \Rules \Methods ;
4+
5+ use PhpParser \Node ;
6+ use PHPStan \Analyser \Scope ;
7+ use PHPStan \Node \InClassMethodNode ;
8+ use PHPStan \Reflection \MethodPrototypeReflection ;
9+ use PHPStan \Reflection \Php \PhpMethodFromParserNodeReflection ;
10+ use PHPStan \Rules \Rule ;
11+
12+ /**
13+ * @implements Rule<InClassMethodNode>
14+ */
15+ class OverridingMethodRule implements Rule
16+ {
17+
18+ public function getNodeType (): string
19+ {
20+ return InClassMethodNode::class;
21+ }
22+
23+ public function processNode (Node $ node , Scope $ scope ): array
24+ {
25+ $ method = $ scope ->getFunction ();
26+ if (!$ method instanceof PhpMethodFromParserNodeReflection) {
27+ throw new \PHPStan \ShouldNotHappenException ();
28+ }
29+
30+ $ prototype = $ method ->getPrototype ();
31+ if ($ prototype ->getDeclaringClass ()->getName () === $ method ->getDeclaringClass ()->getName ()) {
32+ return [];
33+ }
34+ if (!$ prototype instanceof MethodPrototypeReflection) {
35+ return [];
36+ }
37+
38+ $ messages = [];
39+ if ($ prototype ->isFinal ()) {
40+ $ messages [] = sprintf (
41+ 'Method %s::%s() overrides final method %s::%s(). ' ,
42+ $ method ->getDeclaringClass ()->getName (),
43+ $ method ->getName (),
44+ $ prototype ->getDeclaringClass ()->getName (),
45+ $ prototype ->getName ()
46+ );
47+ }
48+
49+ return $ messages ;
50+ }
51+
52+ }
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace PHPStan \Rules \Methods ;
4+
5+ use PHPStan \Rules \Rule ;
6+ use PHPStan \Testing \RuleTestCase ;
7+
8+ /**
9+ * @extends RuleTestCase<OverridingMethodRule>
10+ */
11+ class OverridingMethodRuleTest extends RuleTestCase
12+ {
13+
14+ protected function getRule (): Rule
15+ {
16+ return new OverridingMethodRule ();
17+ }
18+
19+ public function testOverridingFinalMethod (): void
20+ {
21+ if (!self ::$ useStaticReflectionProvider ) {
22+ $ this ->markTestSkipped ('Test requires static reflection. ' );
23+ }
24+
25+ $ this ->analyse ([__DIR__ . '/data/overriding-final-method.php ' ], [
26+ [
27+ 'Method OverridingFinalMethod\Bar::doFoo() overrides final method OverridingFinalMethod\Foo::doFoo(). ' ,
28+ 18 ,
29+ ],
30+ ]);
31+ }
32+
33+ }
Original file line number Diff line number Diff line change 1+ <?php
2+
3+ namespace OverridingFinalMethod ;
4+
5+ class Foo
6+ {
7+
8+ final public function doFoo ()
9+ {
10+
11+ }
12+
13+ }
14+
15+ class Bar extends Foo
16+ {
17+
18+ public function doFoo ()
19+ {
20+
21+ }
22+
23+ }
You can’t perform that action at this time.
0 commit comments