Skip to content

Commit

Permalink
Fixed method visibility when the visibility is modified in trait use
Browse files Browse the repository at this point in the history
  • Loading branch information
kukulich committed Sep 17, 2021
1 parent 5d23e18 commit 0fb2ac2
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 6 deletions.
58 changes: 52 additions & 6 deletions src/Reflection/ReflectionClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class ReflectionClass implements Reflection
/** @var array<string, string>|null */
private ?array $cachedTraitAliases = null;

/** @var array<string, int>|null */
private ?array $cachedTraitModifiers = null;

/** @var array<string, string>|null */
private ?array $cachedTraitPrecedences = null;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1020,16 +1030,44 @@ 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<string, int>
*
* @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<string, int> */
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;
}

/** @var Node\Stmt\TraitUse[] $traitUsages */
$traitUsages = array_filter($this->node->stmts, static fn (Node $node): bool => $node instanceof TraitUse);

$this->cachedTraitAliases = [];
$this->cachedTraitModifiers = [];
$this->cachedTraitPrecedences = [];

foreach ($traitUsages as $traitUsage) {
Expand All @@ -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) {
Expand All @@ -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;
}
Expand Down
27 changes: 27 additions & 0 deletions test/unit/Reflection/ReflectionClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'
<?php
trait BarTrait {
private function privateMethod() {}
protected function protectedMethod() {}
}
class Foo
{
use BarTrait {
protectedMethod as public;
privateMethod as protected;
}
}
PHP;

$reflection = (new ClassReflector(new StringSourceLocator($php, $this->astLocator)))->reflect('Foo');
$protectedMethod = $reflection->getMethod('protectedMethod');
self::assertTrue($protectedMethod->isPublic());

$privateMethod = $reflection->getMethod('privateMethod');
self::assertTrue($privateMethod->isProtected());
}
}

0 comments on commit 0fb2ac2

Please sign in to comment.