Skip to content

Commit 3aaa022

Browse files
authored
Merge pull request #1474 from kukulich/final-hook
Property hooks can be marked `final` - expose that in reflection API
2 parents 9a9ca56 + 166e4ca commit 3aaa022

File tree

7 files changed

+65
-50
lines changed

7 files changed

+65
-50
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"php": "~8.2.0 || ~8.3.2 || ~8.4.1",
77
"ext-json": "*",
88
"jetbrains/phpstorm-stubs": "2024.3",
9-
"nikic/php-parser": "^5.3.1"
9+
"nikic/php-parser": "^5.4.0"
1010
},
1111
"authors": [
1212
{

composer.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

psalm-baseline.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
<code><![CDATA[classExists]]></code>
167167
<code><![CDATA[isAbstract]]></code>
168168
<code><![CDATA[isFinal]]></code>
169+
<code><![CDATA[isFinal]]></code>
169170
<code><![CDATA[isPrivate]]></code>
170171
<code><![CDATA[isProtected]]></code>
171172
<code><![CDATA[isPublic]]></code>
@@ -209,7 +210,8 @@
209210
<code><![CDATA[getEndLine]]></code>
210211
<code><![CDATA[getStartLine]]></code>
211212
<code><![CDATA[getStmts]]></code>
212-
<code><![CDATA[getStmts]]></code>
213+
<code><![CDATA[isAbstract]]></code>
214+
<code><![CDATA[isFinal]]></code>
213215
<code><![CDATA[isPrivate]]></code>
214216
<code><![CDATA[isPrivateSet]]></code>
215217
<code><![CDATA[isProtected]]></code>

src/Reflection/ReflectionMethod.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private function __construct(
5353
assert($node instanceof MethodNode || $node instanceof Node\PropertyHook);
5454

5555
$this->name = $name;
56-
$this->modifiers = $node instanceof MethodNode ? $this->computeModifiers($node) : 0;
56+
$this->modifiers = $this->computeModifiers($node);
5757

5858
$this->fillFromNode($node);
5959
}
@@ -305,13 +305,18 @@ public function getModifiers(): int
305305
}
306306

307307
/** @return int-mask-of<ReflectionMethodAdapter::IS_*> */
308-
private function computeModifiers(MethodNode $node): int
308+
private function computeModifiers(MethodNode|Node\PropertyHook $node): int
309309
{
310-
$modifiers = $node->isStatic() ? CoreReflectionMethod::IS_STATIC : 0;
311-
$modifiers += $node->isPublic() ? CoreReflectionMethod::IS_PUBLIC : 0;
312-
$modifiers += $node->isProtected() ? CoreReflectionMethod::IS_PROTECTED : 0;
313-
$modifiers += $node->isPrivate() ? CoreReflectionMethod::IS_PRIVATE : 0;
314-
$modifiers += $node->isAbstract() ? CoreReflectionMethod::IS_ABSTRACT : 0;
310+
$modifiers = 0;
311+
312+
if ($node instanceof MethodNode) {
313+
$modifiers += $node->isStatic() ? CoreReflectionMethod::IS_STATIC : 0;
314+
$modifiers += $node->isPublic() ? CoreReflectionMethod::IS_PUBLIC : 0;
315+
$modifiers += $node->isProtected() ? CoreReflectionMethod::IS_PROTECTED : 0;
316+
$modifiers += $node->isPrivate() ? CoreReflectionMethod::IS_PRIVATE : 0;
317+
$modifiers += $node->isAbstract() ? CoreReflectionMethod::IS_ABSTRACT : 0;
318+
}
319+
315320
$modifiers += $node->isFinal() ? CoreReflectionMethod::IS_FINAL : 0;
316321

317322
return $modifiers;

src/Reflection/ReflectionProperty.php

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use Closure;
88
use Error;
99
use OutOfBoundsException;
10-
use PhpParser\Modifiers;
1110
use PhpParser\Node;
1211
use PhpParser\Node\Stmt\Property as PropertyNode;
1312
use PhpParser\NodeTraverser;
@@ -35,7 +34,6 @@
3534

3635
use function array_map;
3736
use function assert;
38-
use function count;
3937
use function func_num_args;
4038
use function is_object;
4139
use function sprintf;
@@ -675,8 +673,8 @@ private function computeModifiers(PropertyNode $node): int
675673
$modifiers += $node->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0;
676674
$modifiers += $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0;
677675
$modifiers += $node->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0;
678-
$modifiers += ($node->flags & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY) === ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0;
679-
$modifiers += ($node->flags & Modifiers::ABSTRACT) === Modifiers::ABSTRACT ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0;
676+
$modifiers += $node->isFinal() ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0;
677+
$modifiers += $node->isAbstract() ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0;
680678

681679
/** @phpstan-ignore return.type */
682680
return $modifiers;
@@ -699,51 +697,29 @@ private function computeImmediateVirtual(PropertyNode $node): bool
699697
}
700698
}
701699

702-
if ($setHook !== null && ! $this->computeImmediateVirtualBasedOnSetHook($setHook)) {
700+
if ($setHook !== null && ! $this->computeImmediateVirtualBasedOnHook($setHook)) {
703701
return false;
704702
}
705703

706704
if ($getHook === null) {
707705
return true;
708706
}
709707

710-
return $this->computeImmediateVirtualBasedOnGetHook($getHook);
708+
return $this->computeImmediateVirtualBasedOnHook($getHook);
711709
}
712710

713-
private function computeImmediateVirtualBasedOnGetHook(Node\PropertyHook $getHook): bool
711+
private function computeImmediateVirtualBasedOnHook(Node\PropertyHook $hook): bool
714712
{
715-
$getHookBody = $getHook->getStmts();
713+
$hookBody = $hook->getStmts();
716714

717715
// Abstract property or property in interface
718-
if ($getHookBody === null) {
716+
if ($hookBody === null) {
719717
return true;
720718
}
721719

722-
return ! $this->isHookUsingThisProperty($getHook);
723-
}
724-
725-
private function computeImmediateVirtualBasedOnSetHook(Node\PropertyHook $setHook): bool
726-
{
727-
$setHookBody = $setHook->getStmts();
728-
729-
// Abstract property or property in interface
730-
if ($setHookBody === null) {
731-
return true;
732-
}
733-
734-
// Short syntax
735-
if (count($setHookBody) === 1 && $setHookBody[0] instanceof Node\Stmt\Return_) {
736-
return false;
737-
}
738-
739-
return ! $this->isHookUsingThisProperty($setHook);
740-
}
741-
742-
private function isHookUsingThisProperty(Node\PropertyHook $hook): bool
743-
{
744720
$visitor = new FindingVisitor(static fn (Node $node): bool => $node instanceof Node\Expr\PropertyFetch);
745721
$traverser = new NodeTraverser($visitor);
746-
$traverser->traverse([$hook]);
722+
$traverser->traverse($hookBody);
747723

748724
foreach ($visitor->getFoundNodes() as $propertyFetchNode) {
749725
assert($propertyFetchNode instanceof Node\Expr\PropertyFetch);
@@ -754,11 +730,11 @@ private function isHookUsingThisProperty(Node\PropertyHook $hook): bool
754730
&& $propertyFetchNode->name instanceof Node\Identifier
755731
&& $propertyFetchNode->name->name === $this->name
756732
) {
757-
return true;
733+
return false;
758734
}
759735
}
760736

761-
return false;
737+
return true;
762738
}
763739

764740
/** @return array{get?: ReflectionMethod, set?: ReflectionMethod} */

test/unit/Fixture/PropertyHooks.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,14 @@ class GetPropertyHooksReturnTypes
153153
public $hookWithoutType { get => 'string'; }
154154

155155
}
156+
157+
class FinalPropertyHooks
158+
{
159+
public string $notFinalHook {
160+
set => strtolower($value);
161+
}
162+
163+
public string $finalHook {
164+
final set => strtolower($value);
165+
}
166+
}

test/unit/Reflection/ReflectionMethodTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,4 +882,25 @@ public function testGetPropertyHookReturnType(string $propertyName, string|null
882882
self::assertNotNull($getHookReflection);
883883
self::assertSame($returnType, $getHookReflection->getReturnType()?->__toString());
884884
}
885+
886+
/** @return list<array{0: non-empty-string, 1: bool}> */
887+
public static function finalPropertyHookProvider(): array
888+
{
889+
return [
890+
['notFinalHook', false],
891+
['finalHook', true],
892+
];
893+
}
894+
895+
#[DataProvider('finalPropertyHookProvider')]
896+
public function testFinalPropertyHook(string $propertyName, bool $isFinal): void
897+
{
898+
$reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));
899+
$classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\FinalPropertyHooks');
900+
901+
$hookProperty = $classInfo->getProperty($propertyName);
902+
$hookReflection = $hookProperty->getHook(ReflectionPropertyHookType::Set);
903+
self::assertNotNull($hookReflection);
904+
self::assertSame($isFinal, $hookReflection->isFinal());
905+
}
885906
}

0 commit comments

Comments
 (0)