Skip to content

Commit a95d0a8

Browse files
committed
[PropertyInfo] Add an extractor to guess if a property is initializable
1 parent 833909b commit a95d0a8

File tree

12 files changed

+158
-14
lines changed

12 files changed

+158
-14
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<argument type="collection" />
1313
<argument type="collection" />
1414
<argument type="collection" />
15+
<argument type="collection" />
1516
</service>
1617
<service id="Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface" alias="property_info" />
1718

@@ -20,6 +21,7 @@
2021
<tag name="property_info.list_extractor" priority="-1000" />
2122
<tag name="property_info.type_extractor" priority="-1002" />
2223
<tag name="property_info.access_extractor" priority="-1000" />
24+
<tag name="property_info.initializable_extractor" priority="-1000" />
2325
</service>
2426
</services>
2527
</container>

src/Symfony/Component/PropertyInfo/DependencyInjection/PropertyInfoPass.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ class PropertyInfoPass implements CompilerPassInterface
3030
private $typeExtractorTag;
3131
private $descriptionExtractorTag;
3232
private $accessExtractorTag;
33+
private $initializableExtractorTag;
3334

34-
public function __construct(string $propertyInfoService = 'property_info', string $listExtractorTag = 'property_info.list_extractor', string $typeExtractorTag = 'property_info.type_extractor', string $descriptionExtractorTag = 'property_info.description_extractor', string $accessExtractorTag = 'property_info.access_extractor')
35+
public function __construct(string $propertyInfoService = 'property_info', string $listExtractorTag = 'property_info.list_extractor', string $typeExtractorTag = 'property_info.type_extractor', string $descriptionExtractorTag = 'property_info.description_extractor', string $accessExtractorTag = 'property_info.access_extractor', string $initializableExtractorTag = 'property_info.initializable_extractor')
3536
{
3637
$this->propertyInfoService = $propertyInfoService;
3738
$this->listExtractorTag = $listExtractorTag;
3839
$this->typeExtractorTag = $typeExtractorTag;
3940
$this->descriptionExtractorTag = $descriptionExtractorTag;
4041
$this->accessExtractorTag = $accessExtractorTag;
42+
$this->initializableExtractorTag = $initializableExtractorTag;
4143
}
4244

4345
/**
@@ -62,5 +64,8 @@ public function process(ContainerBuilder $container)
6264

6365
$accessExtractors = $this->findAndSortTaggedServices($this->accessExtractorTag, $container);
6466
$definition->replaceArgument(3, new IteratorArgument($accessExtractors));
67+
68+
$initializableExtractors = $this->findAndSortTaggedServices($this->initializableExtractorTag, $container);
69+
$definition->replaceArgument(4, new IteratorArgument($initializableExtractors));
6570
}
6671
}

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

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Inflector\Inflector;
1515
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
16+
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
1617
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
1718
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1819
use Symfony\Component\PropertyInfo\Type;
@@ -24,7 +25,7 @@
2425
*
2526
* @final
2627
*/
27-
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
28+
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface
2829
{
2930
/**
3031
* @internal
@@ -146,6 +147,33 @@ public function isWritable($class, $property, array $context = array())
146147
return null !== $reflectionMethod;
147148
}
148149

150+
151+
/**
152+
* {@inheritdoc}
153+
*/
154+
public function isInitializable(string $class, string $property, array $context = array()): ?bool
155+
{
156+
try {
157+
$reflectionClass = new \ReflectionClass($class);
158+
} catch (\ReflectionException $e) {
159+
return null;
160+
}
161+
162+
if ($constructor = $reflectionClass->getConstructor()) {
163+
foreach ($constructor->getParameters() as $parameter) {
164+
if ($property !== $parameter->name) {
165+
continue;
166+
}
167+
168+
return true;
169+
}
170+
} elseif ($parentClass = $reflectionClass->getParentClass()) {
171+
return $this->isInitializable($parentClass->getName(), $property);
172+
}
173+
174+
return false;
175+
}
176+
149177
/**
150178
* @return Type[]|null
151179
*/

src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
*
2121
* @final
2222
*/
23-
class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface
23+
class PropertyInfoCacheExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface
2424
{
2525
private $propertyInfoExtractor;
2626
private $cacheItemPool;
@@ -80,6 +80,14 @@ public function getTypes($class, $property, array $context = array())
8080
return $this->extract('getTypes', array($class, $property, $context));
8181
}
8282

83+
/**
84+
* {@inheritdoc}
85+
*/
86+
public function isInitializable(string $class, string $property, array $context = array()): ?bool
87+
{
88+
return $this->extract('isInitializable', array($class, $property, $context));
89+
}
90+
8391
/**
8492
* Retrieves the cached data if applicable or delegates to the decorated extractor.
8593
*

src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,28 @@
1818
*
1919
* @final
2020
*/
21-
class PropertyInfoExtractor implements PropertyInfoExtractorInterface
21+
class PropertyInfoExtractor implements PropertyInfoExtractorInterface, PropertyInitializableExtractorInterface
2222
{
2323
private $listExtractors;
2424
private $typeExtractors;
2525
private $descriptionExtractors;
2626
private $accessExtractors;
27+
private $initializableExtractors;
2728

2829
/**
29-
* @param iterable|PropertyListExtractorInterface[] $listExtractors
30-
* @param iterable|PropertyTypeExtractorInterface[] $typeExtractors
31-
* @param iterable|PropertyDescriptionExtractorInterface[] $descriptionExtractors
32-
* @param iterable|PropertyAccessExtractorInterface[] $accessExtractors
30+
* @param iterable|PropertyListExtractorInterface[] $listExtractors
31+
* @param iterable|PropertyTypeExtractorInterface[] $typeExtractors
32+
* @param iterable|PropertyDescriptionExtractorInterface[] $descriptionExtractors
33+
* @param iterable|PropertyAccessExtractorInterface[] $accessExtractors
34+
* @param iterable|PropertyInitializableExtractorInterface[] $initializableExtractors
3335
*/
34-
public function __construct(iterable $listExtractors = array(), iterable $typeExtractors = array(), iterable $descriptionExtractors = array(), iterable $accessExtractors = array())
36+
public function __construct(iterable $listExtractors = array(), iterable $typeExtractors = array(), iterable $descriptionExtractors = array(), iterable $accessExtractors = array(), iterable $initializableExtractors = array())
3537
{
3638
$this->listExtractors = $listExtractors;
3739
$this->typeExtractors = $typeExtractors;
3840
$this->descriptionExtractors = $descriptionExtractors;
3941
$this->accessExtractors = $accessExtractors;
42+
$this->initializableExtractors = $initializableExtractors;
4043
}
4144

4245
/**
@@ -87,6 +90,14 @@ public function isWritable($class, $property, array $context = array())
8790
return $this->extract($this->accessExtractors, 'isWritable', array($class, $property, $context));
8891
}
8992

93+
/**
94+
* {@inheritdoc}
95+
*/
96+
public function isInitializable(string $class, string $property, array $context = array()): ?bool
97+
{
98+
return $this->extract($this->initializableExtractors, 'isInitializable', array($class, $property, $context));
99+
}
100+
90101
/**
91102
* Iterates over registered extractors and return the first value found.
92103
*
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\PropertyInfo;
13+
14+
/**
15+
* Guesses if the property can be initialized through the constructor.
16+
*
17+
* @author Kévin Dunglas <dunglas@gmail.com>
18+
*/
19+
interface PropertyInitializableExtractorInterface
20+
{
21+
/**
22+
* Is the property initializable?
23+
*/
24+
public function isInitializable(string $class, string $property, array $context = array()): ?bool;
25+
}

src/Symfony/Component/PropertyInfo/Tests/AbstractPropertyInfoExtractorTest.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
16+
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
1617
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor;
1718
use Symfony\Component\PropertyInfo\Tests\Fixtures\NullExtractor;
1819
use Symfony\Component\PropertyInfo\Type;
@@ -30,7 +31,7 @@ class AbstractPropertyInfoExtractorTest extends TestCase
3031
protected function setUp()
3132
{
3233
$extractors = array(new NullExtractor(), new DummyExtractor());
33-
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors);
34+
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors, $extractors);
3435
}
3536

3637
public function testInstanceOf()
@@ -39,6 +40,7 @@ public function testInstanceOf()
3940
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface', $this->propertyInfo);
4041
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface', $this->propertyInfo);
4142
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface', $this->propertyInfo);
43+
$this->assertInstanceOf(PropertyInitializableExtractorInterface::class, $this->propertyInfo);
4244
}
4345

4446
public function testGetShortDescription()
@@ -70,4 +72,9 @@ public function testGetProperties()
7072
{
7173
$this->assertEquals(array('a', 'b'), $this->propertyInfo->getProperties('Foo'));
7274
}
75+
76+
public function testIsInitializable()
77+
{
78+
$this->assertTrue($this->propertyInfo->isInitializable('Foo', 'bar', array()));
79+
}
7380
}

src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoPassTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function testServicesAreOrderedAccordingToPriority($index, $tag)
2626
{
2727
$container = new ContainerBuilder();
2828

29-
$definition = $container->register('property_info')->setArguments(array(null, null, null, null));
29+
$definition = $container->register('property_info')->setArguments(array(null, null, null, null, null));
3030
$container->register('n2')->addTag($tag, array('priority' => 100));
3131
$container->register('n1')->addTag($tag, array('priority' => 200));
3232
$container->register('n3')->addTag($tag);
@@ -49,14 +49,15 @@ public function provideTags()
4949
array(1, 'property_info.type_extractor'),
5050
array(2, 'property_info.description_extractor'),
5151
array(3, 'property_info.access_extractor'),
52+
array(4, 'property_info.initializable_extractor'),
5253
);
5354
}
5455

5556
public function testReturningEmptyArrayWhenNoService()
5657
{
5758
$container = new ContainerBuilder();
5859
$propertyInfoExtractorDefinition = $container->register('property_info')
59-
->setArguments(array(array(), array(), array(), array()));
60+
->setArguments(array(array(), array(), array(), array(), array()));
6061

6162
$propertyInfoPass = new PropertyInfoPass();
6263
$propertyInfoPass->process($container);
@@ -65,5 +66,6 @@ public function testReturningEmptyArrayWhenNoService()
6566
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(1));
6667
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(2));
6768
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(3));
69+
$this->assertEquals(new IteratorArgument(array()), $propertyInfoExtractorDefinition->getArgument(4));
6870
}
6971
}

src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
1616
use Symfony\Component\PropertyInfo\Tests\Fixtures\AdderRemoverDummy;
17+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy;
18+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyChild3;
19+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended;
20+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2;
1721
use Symfony\Component\PropertyInfo\Type;
1822

1923
/**
@@ -275,4 +279,24 @@ public function testSingularize()
275279
$this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'feet'));
276280
$this->assertEquals(array('analyses', 'feet'), $this->extractor->getProperties(AdderRemoverDummy::class));
277281
}
282+
283+
/**
284+
* @dataProvider getInitializableProperties
285+
*/
286+
public function testIsInitializable(string $class, string $property, bool $expected)
287+
{
288+
$this->assertSame($expected, $this->extractor->isInitializable($class, $property));
289+
}
290+
291+
public function getInitializableProperties(): array
292+
{
293+
return array(
294+
array(Php71Dummy::class, 'string', true),
295+
array(Php71Dummy::class, 'intPrivate', true),
296+
array(Php71Dummy::class, 'notExist', false),
297+
array(Php71DummyExtended::class, 'intWithAccessor', true),
298+
array(Php71DummyExtended2::class, 'intWithAccessor', true),
299+
array(Php71DummyExtended2::class, 'intPrivate', false),
300+
);
301+
}
278302
}

src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,15 @@
1313

1414
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
1515
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
16+
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
1617
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
1718
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
1819
use Symfony\Component\PropertyInfo\Type;
1920

2021
/**
2122
* @author Kévin Dunglas <dunglas@gmail.com>
2223
*/
23-
class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
24+
class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface
2425
{
2526
/**
2627
* {@inheritdoc}
@@ -69,4 +70,12 @@ public function getProperties($class, array $context = array())
6970
{
7071
return array('a', 'b');
7172
}
73+
74+
/**
75+
* {@inheritdoc}
76+
*/
77+
public function isInitializable(string $class, string $property, array $context = array()): ?bool
78+
{
79+
return true;
80+
}
7281
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy