diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php index 4a6a296784d6d..f833731aa6dee 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php @@ -45,7 +45,7 @@ final class PhpStanExtractor implements PropertyTypeExtractorInterface, Construc /** @var NameScopeFactory */ private $nameScopeFactory; - /** @var array */ + /** @var array */ private $docBlocks = []; private $phpStanTypeHelper; private $mutatorPrefixes; @@ -72,8 +72,8 @@ public function __construct(array $mutatorPrefixes = null, array $accessorPrefix public function getTypes(string $class, string $property, array $context = []): ?array { /** @var PhpDocNode|null $docNode */ - [$docNode, $source, $prefix] = $this->getDocBlock($class, $property); - $nameScope = $this->nameScopeFactory->create($class); + [$docNode, $source, $prefix, $declaringClass] = $this->getDocBlock($class, $property); + $nameScope = $this->nameScopeFactory->create($class, $declaringClass); if (null === $docNode) { return null; } @@ -184,7 +184,7 @@ private function filterDocBlockParams(PhpDocNode $docNode, string $allowedParam) } /** - * @return array{PhpDocNode|null, int|null, string|null} + * @return array{PhpDocNode|null, int|null, string|null, string|null} */ private function getDocBlock(string $class, string $property): array { @@ -196,20 +196,23 @@ private function getDocBlock(string $class, string $property): array $ucFirstProperty = ucfirst($property); - if ($docBlock = $this->getDocBlockFromProperty($class, $property)) { - $data = [$docBlock, self::PROPERTY, null]; - } elseif ([$docBlock] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR)) { - $data = [$docBlock, self::ACCESSOR, null]; - } elseif ([$docBlock, $prefix] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR)) { - $data = [$docBlock, self::MUTATOR, $prefix]; + if ([$docBlock, $declaringClass] = $this->getDocBlockFromProperty($class, $property)) { + $data = [$docBlock, self::PROPERTY, null, $declaringClass]; + } elseif ([$docBlock, $_, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR)) { + $data = [$docBlock, self::ACCESSOR, null, $declaringClass]; + } elseif ([$docBlock, $prefix, $declaringClass] = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR)) { + $data = [$docBlock, self::MUTATOR, $prefix, $declaringClass]; } else { - $data = [null, null, null]; + $data = [null, null, null, null]; } return $this->docBlocks[$propertyHash] = $data; } - private function getDocBlockFromProperty(string $class, string $property): ?PhpDocNode + /** + * @return array{PhpDocNode, string}|null + */ + private function getDocBlockFromProperty(string $class, string $property): ?array { // Use a ReflectionProperty instead of $class to get the parent class if applicable try { @@ -226,11 +229,11 @@ private function getDocBlockFromProperty(string $class, string $property): ?PhpD $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); - return $phpDocNode; + return [$phpDocNode, $reflectionProperty->class]; } /** - * @return array{PhpDocNode, string}|null + * @return array{PhpDocNode, string, string}|null */ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, int $type): ?array { @@ -269,6 +272,6 @@ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, i $phpDocNode = $this->phpDocParser->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_END); - return [$phpDocNode, $prefix]; + return [$phpDocNode, $prefix, $reflectionMethod->class]; } } diff --git a/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php b/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php index 6722c0fb01f60..7d9a5f9ac1a58 100644 --- a/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php +++ b/src/Symfony/Component/PropertyInfo/PhpStan/NameScope.php @@ -22,14 +22,14 @@ */ final class NameScope { - private $className; + private $calledClassName; private $namespace; /** @var array alias(string) => fullName(string) */ private $uses; - public function __construct(string $className, string $namespace, array $uses = []) + public function __construct(string $calledClassName, string $namespace, array $uses = []) { - $this->className = $className; + $this->calledClassName = $calledClassName; $this->namespace = $namespace; $this->uses = $uses; } @@ -60,6 +60,6 @@ public function resolveStringName(string $name): string public function resolveRootClass(): string { - return $this->resolveStringName($this->className); + return $this->resolveStringName($this->calledClassName); } } diff --git a/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php b/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php index 1243259607c22..32f2f330eafcb 100644 --- a/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php +++ b/src/Symfony/Component/PropertyInfo/PhpStan/NameScopeFactory.php @@ -20,16 +20,18 @@ */ final class NameScopeFactory { - public function create(string $fullClassName): NameScope + public function create(string $calledClassName, string $declaringClassName = null): NameScope { - $reflection = new \ReflectionClass($fullClassName); - $path = explode('\\', $fullClassName); - $className = array_pop($path); - [$namespace, $uses] = $this->extractFromFullClassName($reflection); + $declaringClassName = $declaringClassName ?? $calledClassName; - $uses = array_merge($uses, $this->collectUses($reflection)); + $path = explode('\\', $calledClassName); + $calledClassName = array_pop($path); - return new NameScope($className, $namespace, $uses); + $declaringReflection = new \ReflectionClass($declaringClassName); + [$declaringNamespace, $declaringUses] = $this->extractFromFullClassName($declaringReflection); + $declaringUses = array_merge($declaringUses, $this->collectUses($declaringReflection)); + + return new NameScope($calledClassName, $declaringNamespace, $declaringUses); } private function collectUses(\ReflectionClass $reflection): array diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php index 30f6b831ac748..d3c2c950963b1 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\PropertyInfo\Tests\Extractor; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\PhpStanExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; @@ -21,6 +22,8 @@ use Symfony\Component\PropertyInfo\Tests\Fixtures\TraitUsage\DummyUsingTrait; use Symfony\Component\PropertyInfo\Type; +require_once __DIR__.'/../Fixtures/Extractor/DummyNamespace.php'; + /** * @author Baptiste Leduc */ @@ -31,9 +34,15 @@ class PhpStanExtractorTest extends TestCase */ private $extractor; + /** + * @var PhpDocExtractor + */ + private $phpDocExtractor; + protected function setUp(): void { $this->extractor = new PhpStanExtractor(); + $this->phpDocExtractor = new PhpDocExtractor(); } /** @@ -383,6 +392,15 @@ public function testDummyNamespace() $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DummyNamespace', 'dummy') ); } + + public function testDummyNamespaceWithProperty() + { + $phpStanTypes = $this->extractor->getTypes(\B\Dummy::class, 'property'); + $phpDocTypes = $this->phpDocExtractor->getTypes(\B\Dummy::class, 'property'); + + $this->assertEquals('A\Property', $phpStanTypes[0]->getClassName()); + $this->assertEquals($phpDocTypes[0]->getClassName(), $phpStanTypes[0]->getClassName()); + } } class PhpStanOmittedParamTagTypeDocBlock diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Extractor/DummyNamespace.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Extractor/DummyNamespace.php new file mode 100644 index 0000000000000..fd590af64709e --- /dev/null +++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Extractor/DummyNamespace.php @@ -0,0 +1,20 @@ +