diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php index a0a97fb2242f..c56dbdc2cb99 100644 --- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php +++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php @@ -42,6 +42,10 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp */ public static $defaultArrayMutatorPrefixes = ['add', 'remove']; + public const ALLOW_PRIVATE = 1; + public const ALLOW_PROTECTED = 2; + public const ALLOW_PUBLIC = 4; + private const MAP_TYPES = [ 'integer' => Type::BUILTIN_TYPE_INT, 'boolean' => Type::BUILTIN_TYPE_BOOL, @@ -52,18 +56,20 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp private $accessorPrefixes; private $arrayMutatorPrefixes; private $enableConstructorExtraction; + private $accessFlags; /** * @param string[]|null $mutatorPrefixes * @param string[]|null $accessorPrefixes * @param string[]|null $arrayMutatorPrefixes */ - public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true) + public function __construct(array $mutatorPrefixes = null, array $accessorPrefixes = null, array $arrayMutatorPrefixes = null, bool $enableConstructorExtraction = true, int $accessFlags = self::ALLOW_PUBLIC) { $this->mutatorPrefixes = null !== $mutatorPrefixes ? $mutatorPrefixes : self::$defaultMutatorPrefixes; $this->accessorPrefixes = null !== $accessorPrefixes ? $accessorPrefixes : self::$defaultAccessorPrefixes; $this->arrayMutatorPrefixes = null !== $arrayMutatorPrefixes ? $arrayMutatorPrefixes : self::$defaultArrayMutatorPrefixes; $this->enableConstructorExtraction = $enableConstructorExtraction; + $this->accessFlags = $accessFlags; } /** @@ -77,16 +83,34 @@ public function getProperties($class, array $context = []) return; } + $propertyFlags = 0; + $methodFlags = 0; + + if ($this->accessFlags & self::ALLOW_PUBLIC) { + $propertyFlags = $propertyFlags | \ReflectionProperty::IS_PUBLIC; + $methodFlags = $methodFlags | \ReflectionMethod::IS_PUBLIC; + } + + if ($this->accessFlags & self::ALLOW_PRIVATE) { + $propertyFlags = $propertyFlags | \ReflectionProperty::IS_PRIVATE; + $methodFlags = $methodFlags | \ReflectionMethod::IS_PRIVATE; + } + + if ($this->accessFlags & self::ALLOW_PROTECTED) { + $propertyFlags = $propertyFlags | \ReflectionProperty::IS_PROTECTED; + $methodFlags = $methodFlags | \ReflectionMethod::IS_PROTECTED; + } + $reflectionProperties = $reflectionClass->getProperties(); $properties = []; foreach ($reflectionProperties as $reflectionProperty) { - if ($reflectionProperty->isPublic()) { + if ($reflectionProperty->getModifiers() & $propertyFlags) { $properties[$reflectionProperty->name] = $reflectionProperty->name; } } - foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { + foreach ($reflectionClass->getMethods($methodFlags) as $reflectionMethod) { if ($reflectionMethod->isStatic()) { continue; } @@ -134,7 +158,7 @@ public function getTypes($class, $property, array $context = []) */ public function isReadable($class, $property, array $context = []) { - if ($this->isPublicProperty($class, $property)) { + if ($this->isAllowedProperty($class, $property)) { return true; } @@ -148,7 +172,7 @@ public function isReadable($class, $property, array $context = []) */ public function isWritable($class, $property, array $context = []) { - if ($this->isPublicProperty($class, $property)) { + if ($this->isAllowedProperty($class, $property)) { return true; } @@ -317,12 +341,24 @@ private function resolveTypeName(string $name, \ReflectionMethod $reflectionMeth return $name; } - private function isPublicProperty(string $class, string $property): bool + private function isAllowedProperty(string $class, string $property): bool { try { $reflectionProperty = new \ReflectionProperty($class, $property); - return $reflectionProperty->isPublic(); + if ($this->accessFlags & self::ALLOW_PUBLIC && $reflectionProperty->isPublic()) { + return true; + } + + if ($this->accessFlags & self::ALLOW_PROTECTED && $reflectionProperty->isProtected()) { + return true; + } + + if ($this->accessFlags & self::ALLOW_PRIVATE && $reflectionProperty->isPrivate()) { + return true; + } + + return false; } catch (\ReflectionException $e) { // Return false if the property doesn't exist } diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php index 84f3d8f9255d..b7dafe7d8f15 100644 --- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php +++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php @@ -15,6 +15,7 @@ use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\DefaultValue; +use Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\NotInstantiable; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy; use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2; @@ -294,6 +295,27 @@ public function testSingularize() $this->assertEquals(['analyses', 'feet'], $this->extractor->getProperties(AdderRemoverDummy::class)); } + public function testPrivatePropertyExtractor() + { + $privateExtractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PRIVATE | ReflectionExtractor::ALLOW_PROTECTED); + $properties = $privateExtractor->getProperties(Dummy::class); + + $this->assertContains('bar', $properties); + $this->assertContains('baz', $properties); + + $this->assertTrue($privateExtractor->isReadable(Dummy::class, 'bar')); + $this->assertTrue($privateExtractor->isReadable(Dummy::class, 'baz')); + + $protectedExtractor = new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PUBLIC | ReflectionExtractor::ALLOW_PROTECTED); + $properties = $protectedExtractor->getProperties(Dummy::class); + + $this->assertNotContains('bar', $properties); + $this->assertContains('baz', $properties); + + $this->assertFalse($protectedExtractor->isReadable(Dummy::class, 'bar')); + $this->assertTrue($protectedExtractor->isReadable(Dummy::class, 'baz')); + } + /** * @dataProvider getInitializableProperties */