diff --git a/src/Reflection/ReflectionClass.php b/src/Reflection/ReflectionClass.php index de92ae71e..be48f0b09 100644 --- a/src/Reflection/ReflectionClass.php +++ b/src/Reflection/ReflectionClass.php @@ -82,6 +82,9 @@ class ReflectionClass implements Reflection /** @var array|null */ private ?array $cachedTraitAliases = null; + /** @var array|null */ + private ?array $cachedTraitModifiers = null; + /** @var array|null */ private ?array $cachedTraitPrecedences = null; @@ -237,11 +240,18 @@ private function createMethodsFromTrait(ReflectionMethod $method): array { $traitAliases = $this->getTraitAliases(); $traitPrecedences = $this->getTraitPrecedences(); + $traitModifiers = $this->getTraitModifiers(); $methodAst = $method->getAst(); assert($methodAst instanceof ClassMethod); - $methodHash = $this->methodHash($method->getImplementingClass()->getName(), $method->getName()); + $methodHash = $this->methodHash($method->getImplementingClass()->getName(), $method->getName()); + + if (isset($traitModifiers[$methodHash])) { + $methodAst = clone $methodAst; + $methodAst->flags = ($methodAst->flags & ~ Node\Stmt\Class_::VISIBILITY_MODIFIER_MASK) | $traitModifiers[$methodHash]; + } + $createMethod = fn (?string $aliasMethodName): ReflectionMethod => ReflectionMethod::createFromNode( $this->reflector, $methodAst, @@ -1020,9 +1030,36 @@ private function getTraitPrecedences(): array return $this->cachedTraitPrecedences; } + /** + * Return a list of the used modifiers when importing traits for this class. + * The returned array is in key/value pair in this format:. + * + * 'methodName' => 'modifier' + * + * @return array + * + * @example + * // When reflecting a class such as: + * class Foo + * { + * use MyTrait { + * myTraitMethod as public; + * } + * } + * // This method would return + * // ['myTraitMethod' => 1] + */ + private function getTraitModifiers(): array + { + $this->parseTraitUsages(); + + /** @return array */ + return $this->cachedTraitModifiers; + } + private function parseTraitUsages(): void { - if ($this->cachedTraitAliases !== null && $this->cachedTraitPrecedences !== null) { + if ($this->cachedTraitAliases !== null && $this->cachedTraitModifiers !== null && $this->cachedTraitPrecedences !== null) { return; } @@ -1030,6 +1067,7 @@ private function parseTraitUsages(): void $traitUsages = array_filter($this->node->stmts, static fn (Node $node): bool => $node instanceof TraitUse); $this->cachedTraitAliases = []; + $this->cachedTraitModifiers = []; $this->cachedTraitPrecedences = []; foreach ($traitUsages as $traitUsage) { @@ -1042,9 +1080,17 @@ private function parseTraitUsages(): void $usedTrait = end($traitNames); } - if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias && $adaptation->newName) { - $this->cachedTraitAliases[$adaptation->newName->name] = $this->methodHash($usedTrait->toString(), $adaptation->method->toString()); - continue; + $methodHash = $this->methodHash($usedTrait->toString(), $adaptation->method->toString()); + + if ($adaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { + if ($adaptation->newModifier) { + $this->cachedTraitModifiers[$methodHash] = $adaptation->newModifier; + } + + if ($adaptation->newName) { + $this->cachedTraitAliases[$adaptation->newName->name] = $methodHash; + continue; + } } if (! $adaptation instanceof Node\Stmt\TraitUseAdaptation\Precedence || ! $adaptation->insteadof) { @@ -1053,7 +1099,7 @@ private function parseTraitUsages(): void foreach ($adaptation->insteadof as $insteadof) { $adaptationNameHash = $this->methodHash($insteadof->toString(), $adaptation->method->toString()); - $originalNameHash = $this->methodHash($usedTrait->toString(), $adaptation->method->toString()); + $originalNameHash = $methodHash; $this->cachedTraitPrecedences[$adaptationNameHash] = $originalNameHash; } diff --git a/test/unit/Reflection/ReflectionClassTest.php b/test/unit/Reflection/ReflectionClassTest.php index 04f0818a5..4f249af37 100644 --- a/test/unit/Reflection/ReflectionClassTest.php +++ b/test/unit/Reflection/ReflectionClassTest.php @@ -1970,4 +1970,31 @@ class HelloWorld $reflection = (new ClassReflector(new StringSourceLocator($php, $this->astLocator)))->reflect('HelloWorld'); self::assertTrue($reflection->hasMethod('hello')); } + + public function testTraitMethodWithModifiedVisibility(): void + { + $php = <<<'PHP' + astLocator)))->reflect('Foo'); + $protectedMethod = $reflection->getMethod('protectedMethod'); + self::assertTrue($protectedMethod->isPublic()); + + $privateMethod = $reflection->getMethod('privateMethod'); + self::assertTrue($privateMethod->isProtected()); + } }