Skip to content

Commit e5d1e1e

Browse files
committed
feature symfony#53362 [PropertyInfo] Restrict access to PhpStanExtractor based on visibility (nikophil)
This PR was merged into the 7.1 branch. Discussion ---------- [PropertyInfo] Restrict access to `PhpStanExtractor` based on visibility | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | License | MIT Hi, this small PR gives a bit more control on what `PhpStanExtractor` can see or not, based on the visibility of the property or its accessor. It's pretty unusual, but sometimes, the getter does not return the same type than the property itself, and before this PR, there was actually no way to extract the type returned by the getter. `ReflectionExtractor` has this granularity, but it cannot read complex PhpDoc types. Commits ------- e3ec55f [PropertyInfo] restrict access to PhpStanExtractor based on visibility
2 parents f0f3040 + e3ec55f commit e5d1e1e

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

src/Symfony/Component/PropertyInfo/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ CHANGELOG
55
---
66

77
* Introduce `PropertyDocBlockExtractorInterface` to extract a property's doc block
8+
* Restrict access to `PhpStanExtractor` based on visibility
89

910
6.4
1011
---

src/Symfony/Component/PropertyInfo/Extractor/PhpStanExtractor.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ final class PhpStanExtractor implements PropertyTypeExtractorInterface, Construc
5353
* @param list<string>|null $accessorPrefixes
5454
* @param list<string>|null $arrayMutatorPrefixes
5555
*/
56-
public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null)
56+
public function __construct(?array $mutatorPrefixes = null, ?array $accessorPrefixes = null, ?array $arrayMutatorPrefixes = null, private bool $allowPrivateAccess = true)
5757
{
5858
if (!class_exists(ContextFactory::class)) {
5959
throw new \LogicException(sprintf('Unable to use the "%s" class as the "phpdocumentor/type-resolver" package is not installed. Try running composer require "phpdocumentor/type-resolver".', __CLASS__));
@@ -232,6 +232,10 @@ private function getDocBlockFromProperty(string $class, string $property): ?arra
232232
return null;
233233
}
234234

235+
if (!$this->canAccessMemberBasedOnItsVisibility($reflectionProperty)) {
236+
return null;
237+
}
238+
235239
// Type can be inside property docblock as `@var`
236240
$rawDocNode = $reflectionProperty->getDocComment();
237241
$phpDocNode = $rawDocNode ? $this->getPhpDocNode($rawDocNode) : null;
@@ -274,8 +278,11 @@ private function getDocBlockFromMethod(string $class, string $ucFirstProperty, i
274278
}
275279

276280
if (
277-
(self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters())
278-
|| (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1)
281+
(
282+
(self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters())
283+
|| (self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1)
284+
)
285+
&& $this->canAccessMemberBasedOnItsVisibility($reflectionMethod)
279286
) {
280287
break;
281288
}
@@ -305,4 +312,9 @@ private function getPhpDocNode(string $rawDocNode): PhpDocNode
305312

306313
return $phpDocNode;
307314
}
315+
316+
private function canAccessMemberBasedOnItsVisibility(\ReflectionProperty|\ReflectionMethod $member): bool
317+
{
318+
return $this->allowPrivateAccess || $member->isPublic();
319+
}
308320
}

src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpStanExtractorTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummyWithoutDocBlock;
1818
use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue;
1919
use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy;
20+
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyPropertyAndGetterWithDifferentTypes;
2021
use Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy;
2122
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80Dummy;
2223
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php80PromotedDummy;
@@ -475,6 +476,26 @@ public static function php80TypesProvider()
475476
[Php80PromotedDummy::class, 'promoted', null],
476477
];
477478
}
479+
480+
public static function allowPrivateAccessProvider(): array
481+
{
482+
return [
483+
[true, [new Type(Type::BUILTIN_TYPE_STRING)]],
484+
[false, [new Type(Type::BUILTIN_TYPE_ARRAY, collection: true, collectionKeyType: new Type('int'), collectionValueType: new Type('string'))]],
485+
];
486+
}
487+
488+
/**
489+
* @dataProvider allowPrivateAccessProvider
490+
*/
491+
public function testAllowPrivateAccess(bool $allowPrivateAccess, array $expectedTypes)
492+
{
493+
$extractor = new PhpStanExtractor(allowPrivateAccess: $allowPrivateAccess);
494+
$this->assertEquals(
495+
$expectedTypes,
496+
$extractor->getTypes(DummyPropertyAndGetterWithDifferentTypes::class, 'foo')
497+
);
498+
}
478499
}
479500

480501
class PhpStanOmittedParamTagTypeDocBlock
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
6+
7+
final readonly class DummyPropertyAndGetterWithDifferentTypes
8+
{
9+
public function __construct(
10+
/**
11+
* @var string
12+
*/
13+
private string $foo
14+
) {
15+
}
16+
17+
/**
18+
* @return array<int, string>
19+
*/
20+
public function getFoo(): array
21+
{
22+
return (array)$this->foo;
23+
}
24+
}

0 commit comments

Comments
 (0)