diff --git a/src/Reflection/Adapter/ReflectionProperty.php b/src/Reflection/Adapter/ReflectionProperty.php index 526a4ce53..b4a6b10ef 100644 --- a/src/Reflection/Adapter/ReflectionProperty.php +++ b/src/Reflection/Adapter/ReflectionProperty.php @@ -165,6 +165,6 @@ public function getAttributes(?string $name = null, int $flags = 0): array public function isReadOnly(): bool { - throw new Exception\NotImplemented('Not implemented'); + return $this->betterReflectionProperty->isReadOnly(); } } diff --git a/src/Reflection/ReflectionProperty.php b/src/Reflection/ReflectionProperty.php index 5a136df3e..114d3ac97 100644 --- a/src/Reflection/ReflectionProperty.php +++ b/src/Reflection/ReflectionProperty.php @@ -230,6 +230,11 @@ public function isInitialized(?object $object = null): bool } } + public function isReadOnly(): bool + { + return $this->node->isReadonly(); + } + /** * Get the DocBlock type hints as an array of strings. * diff --git a/src/Reflection/StringCast/ReflectionPropertyStringCast.php b/src/Reflection/StringCast/ReflectionPropertyStringCast.php index 38c7565a3..125f42e87 100644 --- a/src/Reflection/StringCast/ReflectionPropertyStringCast.php +++ b/src/Reflection/StringCast/ReflectionPropertyStringCast.php @@ -24,10 +24,11 @@ public static function toString(ReflectionProperty $propertyReflection): string $type = $propertyReflection->getType(); return sprintf( - 'Property [%s %s%s%s $%s ]', + 'Property [%s %s%s%s%s $%s ]', $stateModifier, self::visibilityToString($propertyReflection), $propertyReflection->isStatic() ? ' static' : '', + $propertyReflection->isReadOnly() ? ' readonly' : '', $type !== null ? sprintf(' %s', $type->__toString()) : '', $propertyReflection->getName(), ); diff --git a/test/unit/Fixture/ExampleClass.php b/test/unit/Fixture/ExampleClass.php index 3396578aa..4bb969a2e 100644 --- a/test/unit/Fixture/ExampleClass.php +++ b/test/unit/Fixture/ExampleClass.php @@ -38,6 +38,8 @@ class ExampleClass */ public $publicProperty = __DIR__; + public readonly int $readOnlyProperty; + public static $publicStaticProperty; public function __construct(private ?int $promotedProperty = 123, $noPromotedProperty = null) diff --git a/test/unit/Fixture/ExampleClassExport.txt b/test/unit/Fixture/ExampleClassExport.txt index d3eb75dc7..7db04de83 100644 --- a/test/unit/Fixture/ExampleClassExport.txt +++ b/test/unit/Fixture/ExampleClassExport.txt @@ -1,5 +1,5 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] { - @@ %s/test/unit/Fixture/ExampleClass.php 10-50 + @@ %s/test/unit/Fixture/ExampleClass.php 10-52 - Constants [5] { Constant [ public integer MY_CONST_1 ] { 123 } @@ -16,16 +16,17 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] { - Static methods [0] { } - - Properties [4] { + - Properties [5] { Property [ private $privateProperty ] Property [ protected $protectedProperty ] Property [ public $publicProperty ] + Property [ public readonly int $readOnlyProperty ] Property [ private ?int $promotedProperty ] } - Methods [2] { Method [ public method __construct ] { - @@ %s/test/unit/Fixture/ExampleClass.php 43 - 45 + @@ %s/test/unit/Fixture/ExampleClass.php 45 - 47 - Parameters [2] { Parameter #0 [ ?int $promotedProperty = 123 ] @@ -34,7 +35,7 @@ Class [ class Roave\BetterReflectionTest\Fixture\ExampleClass ] { } Method [ public method someMethod ] { - @@ %s/test/unit/Fixture/ExampleClass.php 47 - 49 + @@ %s/test/unit/Fixture/ExampleClass.php 49 - 51 } } } diff --git a/test/unit/Fixture/StringCastClass.php b/test/unit/Fixture/StringCastClass.php index 38a3fab69..32965af28 100644 --- a/test/unit/Fixture/StringCastClass.php +++ b/test/unit/Fixture/StringCastClass.php @@ -39,6 +39,8 @@ abstract class StringCastClass extends StringCastClassParent implements StringCa public int|bool $unionTypeProperty = false; public ?int $nullableTypeProperty = null; + public readonly int $readOnlyProperty; + public function __construct() { } diff --git a/test/unit/Fixture/StringCastClassExpected.txt b/test/unit/Fixture/StringCastClassExpected.txt index 8202d1e9e..aaf1172fa 100644 --- a/test/unit/Fixture/StringCastClassExpected.txt +++ b/test/unit/Fixture/StringCastClassExpected.txt @@ -1,5 +1,5 @@ Class [ abstract class Roave\BetterReflectionTest\Fixture\StringCastClass extends Roave\BetterReflectionTest\Fixture\StringCastClassParent implements Roave\BetterReflectionTest\Fixture\StringCastClassInterface2, Roave\BetterReflectionTest\Fixture\StringCastClassInterface ] { - @@ %s/Fixture/StringCastClass.php 26-87 + @@ %s/Fixture/StringCastClass.php 26-89 - Constants [4] { Constant [ public boolean PUBLIC_CONSTANT ] { 1 } @@ -14,62 +14,63 @@ Class [ abstract class Roave\BetterReflectionTest\Fixture\StringCastClass - Static methods [1] { Method [ static public method staticPublicMethod ] { - @@ %s/Fixture/StringCastClass.php 68 - 70 + @@ %s/Fixture/StringCastClass.php 70 - 72 } } - - Properties [6] { + - Properties [7] { Property [ private $privateProperty ] Property [ protected $protectedProperty ] Property [ public $publicProperty ] Property [ public int $namedTypeProperty ] Property [ public int|bool $unionTypeProperty ] Property [ public ?int $nullableTypeProperty ] + Property [ public readonly int $readOnlyProperty ] } - Methods [12] { Method [ public method __construct ] { - @@ %s/Fixture/StringCastClass.php 42 - 44 + @@ %s/Fixture/StringCastClass.php 44 - 46 } Method [ public method __destruct ] { - @@ %s/Fixture/StringCastClass.php 46 - 48 + @@ %s/Fixture/StringCastClass.php 48 - 50 } Method [ public method publicMethod ] { - @@ %s/Fixture/StringCastClass.php 50 - 52 + @@ %s/Fixture/StringCastClass.php 52 - 54 } Method [ protected method protectedMethod ] { - @@ %s/Fixture/StringCastClass.php 54 - 56 + @@ %s/Fixture/StringCastClass.php 56 - 58 } Method [ private method privateMethod ] { - @@ %s/Fixture/StringCastClass.php 58 - 60 + @@ %s/Fixture/StringCastClass.php 60 - 62 } Method [ final public method finalPublicMethod ] { - @@ %s/Fixture/StringCastClass.php 62 - 64 + @@ %s/Fixture/StringCastClass.php 64 - 66 } Method [ abstract public method abstractPublicMethod ] { - @@ %s/Fixture/StringCastClass.php 66 - 66 + @@ %s/Fixture/StringCastClass.php 68 - 68 } Method [ public method noVisibility ] { - @@ %s/Fixture/StringCastClass.php 72 - 74 + @@ %s/Fixture/StringCastClass.php 74 - 76 } Method [ public method overwrittenMethod ] { - @@ %s/Fixture/StringCastClass.php 76 - 78 + @@ %s/Fixture/StringCastClass.php 78 - 80 } Method [ public method prototypeMethod ] { - @@ %s/Fixture/StringCastClass.php 80 - 82 + @@ %s/Fixture/StringCastClass.php 82 - 84 } Method [ public method methodWithParameters ] { - @@ %s/Fixture/StringCastClass.php 84 - 86 + @@ %s/Fixture/StringCastClass.php 86 - 88 - Parameters [2] { Parameter #0 [ $a ] diff --git a/test/unit/Fixture/StringCastProperties.php b/test/unit/Fixture/StringCastProperties.php index 4647f9e91..cfc1fff34 100644 --- a/test/unit/Fixture/StringCastProperties.php +++ b/test/unit/Fixture/StringCastProperties.php @@ -12,4 +12,6 @@ class StringCastProperties public int $namedTypeProperty = 0; public int|bool $unionTypeProperty = false; public ?int $nullableTypeProperty = null; + + public readonly int $readOnlyProperty; } diff --git a/test/unit/Reflection/Adapter/ReflectionPropertyTest.php b/test/unit/Reflection/Adapter/ReflectionPropertyTest.php index b47ceb406..948c9a8de 100644 --- a/test/unit/Reflection/Adapter/ReflectionPropertyTest.php +++ b/test/unit/Reflection/Adapter/ReflectionPropertyTest.php @@ -66,7 +66,7 @@ public function methodExpectationProvider(): array ['getDefaultValue', null, null, []], ['isPromoted', null, true, []], ['getAttributes', NotImplemented::class, null, []], - ['isReadOnly', NotImplemented::class, null, []], + ['isReadOnly', null, true, []], ]; } diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php index cb56a8987..c67c17a5f 100644 --- a/test/unit/Reflection/ReflectionClassTest.php +++ b/test/unit/Reflection/ReflectionClassTest.php @@ -386,7 +386,7 @@ public function testGetProperties(): void $properties = $classInfo->getProperties(); self::assertContainsOnlyInstancesOf(ReflectionProperty::class, $properties); - self::assertCount(5, $properties); + self::assertCount(6, $properties); } public function testGetPropertiesDeclaredWithOneKeyword(): void @@ -418,7 +418,7 @@ public function getPropertiesWithFilterDataProvider(): array { return [ [CoreReflectionProperty::IS_STATIC, 1], - [CoreReflectionProperty::IS_PUBLIC, 2], + [CoreReflectionProperty::IS_PUBLIC, 3], [CoreReflectionProperty::IS_PROTECTED, 1], [CoreReflectionProperty::IS_PRIVATE, 2], [ @@ -426,7 +426,7 @@ public function getPropertiesWithFilterDataProvider(): array CoreReflectionProperty::IS_PUBLIC | CoreReflectionProperty::IS_PROTECTED | CoreReflectionProperty::IS_PRIVATE, - 5, + 6, ], ]; } diff --git a/test/unit/Reflection/ReflectionPropertyTest.php b/test/unit/Reflection/ReflectionPropertyTest.php index 9ea5df9c0..31e484942 100644 --- a/test/unit/Reflection/ReflectionPropertyTest.php +++ b/test/unit/Reflection/ReflectionPropertyTest.php @@ -100,6 +100,17 @@ public function testIsStatic(): void self::assertTrue($staticProp->isStatic()); } + public function testIsReadOnly(): void + { + $classInfo = $this->reflector->reflectClass(ExampleClass::class); + + $notReadOnlyProperty = $classInfo->getProperty('publicProperty'); + self::assertFalse($notReadOnlyProperty->isReadOnly()); + + $readOnlyProperty = $classInfo->getProperty('readOnlyProperty'); + self::assertTrue($readOnlyProperty->isReadOnly()); + } + /** * @return array */ diff --git a/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php b/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php index bc897bb79..356bc86fc 100644 --- a/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php +++ b/test/unit/Reflection/StringCast/ReflectionPropertyStringCastTest.php @@ -35,6 +35,7 @@ public function toStringProvider(): array ['namedTypeProperty', 'Property [ public int $namedTypeProperty ]'], ['unionTypeProperty', 'Property [ public int|bool $unionTypeProperty ]'], ['nullableTypeProperty', 'Property [ public ?int $nullableTypeProperty ]'], + ['readOnlyProperty', 'Property [ public readonly int $readOnlyProperty ]'], ]; }