feature #30706 [PropertyInfo] Add possibility to extract private and protected properties in reflection extractor (joelwurtz)

This PR was squashed before being merged into the 4.3-dev branch (closes #30706).

Discussion
----------

[PropertyInfo] Add possibility to extract private and protected properties in reflection extractor

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #30248
| License       | MIT
| Doc PR        | TODO

This PR add the possibility to extract private and protected properties from a class by passing a new argument to the `ReflectionExtractor`

This new argument consist of flag that filters properties, so someone will also be able to use the `ReflectionExtractor` only for private property

```php
new ReflectionExtractor(null, null, null, true, ReflectionExtractor::ALLOW_PRIVATE | ReflectionExtractor::ALLOW_PROTECTED)
```

Flags method was prefered over a list of bool to avoid too many parameters and also be close to the reflection API of PHP

Commits
-------

05e487f3b2 [PropertyInfo] Add possibility to extract private and protected properties in reflection extractor
This commit is contained in:
Fabien Potencier 2019-03-27 07:41:49 +01:00
commit 8952c021c5
2 changed files with 65 additions and 7 deletions

View File

@ -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 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
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 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
*/
public function isReadable($class, $property, array $context = [])
{
if ($this->isPublicProperty($class, $property)) {
if ($this->isAllowedProperty($class, $property)) {
return true;
}
@ -148,7 +172,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
*/
public function isWritable($class, $property, array $context = [])
{
if ($this->isPublicProperty($class, $property)) {
if ($this->isAllowedProperty($class, $property)) {
return true;
}
@ -317,12 +341,24 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
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
}

View File

@ -15,6 +15,7 @@ use PHPUnit\Framework\TestCase;
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 @@ class ReflectionExtractorTest extends TestCase
$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
*/