Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3313be9

Browse files
committed
[PropertyInfo] Add an extractor to guess if a property is initializable
1 parent 1e16a8b commit 3313be9

16 files changed

+193
-14
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
use Symfony\Component\PropertyAccess\PropertyAccessor;
7272
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
7373
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
74+
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
7475
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
7576
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
7677
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
@@ -336,6 +337,8 @@ public function load(array $configs, ContainerBuilder $container)
336337
->addTag('property_info.description_extractor');
337338
$container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class)
338339
->addTag('property_info.access_extractor');
340+
$container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class)
341+
->addTag('property_info.initializable_extractor');
339342
$container->registerForAutoconfiguration(EncoderInterface::class)
340343
->addTag('serializer.encoder');
341344
$container->registerForAutoconfiguration(DecoderInterface::class)

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\PropertyAccessExtractorInterface" alias="property_info" />
1718
<service id="Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface" alias="property_info" />
@@ -24,6 +25,7 @@
2425
<tag name="property_info.list_extractor" priority="-1000" />
2526
<tag name="property_info.type_extractor" priority="-1002" />
2627
<tag name="property_info.access_extractor" priority="-1000" />
28+
<tag name="property_info.initializable_extractor" priority="-1000" />
2729
</service>
2830
</services>
2931
</container>

src/Symfony/Component/PropertyInfo/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.2.0
5+
-----
6+
7+
* added `PropertyInitializableExtractorInterface` to test if a property can be initialized through the constructor and an implementation is `ReflectionExtractor`
8+
49
3.3.0
510
-----
611

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: 30 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,34 @@ public function isWritable($class, $property, array $context = array())
146147
return null !== $reflectionMethod;
147148
}
148149

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

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 <[email protected]>
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 <[email protected]>
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/Extractor/ReflectionExtractorTest.php

Lines changed: 25 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\NotInstantiable;
18+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71Dummy;
19+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended;
20+
use Symfony\Component\PropertyInfo\Tests\Fixtures\Php71DummyExtended2;
1721
use Symfony\Component\PropertyInfo\Type;
1822

1923
/**
@@ -264,4 +268,25 @@ public function testSingularize()
264268
$this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'feet'));
265269
$this->assertEquals(array('analyses', 'feet'), $this->extractor->getProperties(AdderRemoverDummy::class));
266270
}
271+
272+
/**
273+
* @dataProvider getInitializableProperties
274+
*/
275+
public function testIsInitializable(string $class, string $property, bool $expected)
276+
{
277+
$this->assertSame($expected, $this->extractor->isInitializable($class, $property));
278+
}
279+
280+
public function getInitializableProperties(): array
281+
{
282+
return array(
283+
array(Php71Dummy::class, 'string', true),
284+
array(Php71Dummy::class, 'intPrivate', true),
285+
array(Php71Dummy::class, 'notExist', false),
286+
array(Php71DummyExtended::class, 'intWithAccessor', true),
287+
array(Php71DummyExtended2::class, 'intWithAccessor', true),
288+
array(Php71DummyExtended2::class, 'intPrivate', false),
289+
array(NotInstantiable::class, 'foo', false),
290+
);
291+
}
267292
}

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 <[email protected]>
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
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Tests\Fixtures;
13+
14+
/**
15+
* @author Kévin Dunglas <[email protected]>
16+
*/
17+
class NotInstantiable
18+
{
19+
private function __construct(string $foo)
20+
{
21+
}
22+
}

0 commit comments

Comments
 (0)