diff --git a/src/Reflection/ReflectionClass.php b/src/Reflection/ReflectionClass.php index b309cf93d..53a6978b8 100644 --- a/src/Reflection/ReflectionClass.php +++ b/src/Reflection/ReflectionClass.php @@ -1024,10 +1024,9 @@ private function getPropertiesConsideringAlreadyVisitedClasses(AlreadyVisitedCla foreach ($trait->getPropertiesConsideringAlreadyVisitedClasses($alreadyVisitedClasses) as $traitProperty) { $traitPropertyName = $traitProperty->getName(); - if ( - array_key_exists($traitPropertyName, $properties) - || array_key_exists($traitPropertyName, $immediateProperties) - ) { + $existingProperty = $immediateProperties[$traitPropertyName] ?? $properties[$traitPropertyName] ?? null; + + if ($existingProperty !== null && ! $existingProperty->isAbstract()) { continue; } diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php index 669e2903c..7b86126d0 100644 --- a/test/unit/Reflection/ReflectionClassTest.php +++ b/test/unit/Reflection/ReflectionClassTest.php @@ -3169,4 +3169,46 @@ protected function hello(int $i): void self::assertTrue($hello->isProtected()); self::assertCount(1, $hello->getParameters()); } + + public function testInterfacePropertyImplementedInTrait(): void + { + $php = <<<'PHP' +createdAt ??= new \DateTimeImmutable(); + } + } +} + +class Example implements TimestampsInterface +{ + use Timestamps; +} +PHP; + + $classInfo = (new DefaultReflector(new StringSourceLocator($php, $this->astLocator)))->reflectClass('Example'); + + self::assertTrue($classInfo->hasProperty('createdAt')); + + /** @var trait-string $traitClassName */ + $traitClassName = 'Timestamps'; + /** @var class-string $className */ + $className = 'Example'; + + $propertyInfo = $classInfo->getProperty('createdAt'); + self::assertSame($traitClassName, $propertyInfo->getDeclaringClass()->getName()); + self::assertSame($className, $propertyInfo->getImplementingClass()->getName()); + self::assertFalse($propertyInfo->isVirtual()); + self::assertTrue($propertyInfo->isPublic()); + self::assertTrue($propertyInfo->isPrivateSet()); + } }