Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9d2ab9e

Browse filesBrowse files
dunglasfabpot
authored andcommitted
[PropertyInfo] Add an extractor to guess if a property is initializable
1 parent 9ad492f commit 9d2ab9e
Copy full SHA for 9d2ab9e

16 files changed

+200
-14
lines changed

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
use Symfony\Component\PropertyAccess\PropertyAccessor;
7777
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
7878
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
79+
use Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface;
7980
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
8081
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
8182
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
@@ -347,6 +348,8 @@ public function load(array $configs, ContainerBuilder $container)
347348
->addTag('property_info.description_extractor');
348349
$container->registerForAutoconfiguration(PropertyAccessExtractorInterface::class)
349350
->addTag('property_info.access_extractor');
351+
$container->registerForAutoconfiguration(PropertyInitializableExtractorInterface::class)
352+
->addTag('property_info.initializable_extractor');
350353
$container->registerForAutoconfiguration(EncoderInterface::class)
351354
->addTag('serializer.encoder');
352355
$container->registerForAutoconfiguration(DecoderInterface::class)

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,21 @@
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" />
1819
<service id="Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface" alias="property_info" />
1920
<service id="Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface" alias="property_info" />
2021
<service id="Symfony\Component\PropertyInfo\PropertyListExtractorInterface" alias="property_info" />
22+
<service id="Symfony\Component\PropertyInfo\PropertyInitializableExtractorInterface" alias="property_info" />
2123

2224
<!-- Extractor -->
2325
<service id="property_info.reflection_extractor" class="Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor">
2426
<tag name="property_info.list_extractor" priority="-1000" />
2527
<tag name="property_info.type_extractor" priority="-1002" />
2628
<tag name="property_info.access_extractor" priority="-1000" />
29+
<tag name="property_info.initializable_extractor" priority="-1000" />
2730
</service>
2831
</services>
2932
</container>

‎src/Symfony/Component/PropertyInfo/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/CHANGELOG.md
+5Lines 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 (implemented by `ReflectionExtractor`)
8+
49
3.3.0
510
-----
611

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/DependencyInjection/PropertyInfoPass.php
+6-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
+30-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/PropertyInfoCacheExtractor.php
+9-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/PropertyInfoExtractor.php
+17-6Lines 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
*
+25Lines 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? Returns true if a constructor's parameter matches the given property name.
23+
*/
24+
public function isInitializable(string $class, string $property, array $context = array()): ?bool;
25+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Tests/AbstractPropertyInfoExtractorTest.php
+8-1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoPassTest.php
+4-2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
+23Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
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\Php71DummyExtended2;
1720
use Symfony\Component\PropertyInfo\Type;
1821

1922
/**
@@ -270,4 +273,24 @@ public function testSingularize()
270273
$this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'feet'));
271274
$this->assertEquals(array('analyses', 'feet'), $this->extractor->getProperties(AdderRemoverDummy::class));
272275
}
276+
277+
/**
278+
* @dataProvider getInitializableProperties
279+
*/
280+
public function testIsInitializable(string $class, string $property, bool $expected)
281+
{
282+
$this->assertSame($expected, $this->extractor->isInitializable($class, $property));
283+
}
284+
285+
public function getInitializableProperties(): array
286+
{
287+
return array(
288+
array(Php71Dummy::class, 'string', true),
289+
array(Php71Dummy::class, 'intPrivate', true),
290+
array(Php71Dummy::class, 'notExist', false),
291+
array(Php71DummyExtended2::class, 'intWithAccessor', true),
292+
array(Php71DummyExtended2::class, 'intPrivate', false),
293+
array(NotInstantiable::class, 'foo', false),
294+
);
295+
}
273296
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php
+10-1Lines 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
}
+22Lines 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 <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\Tests\Fixtures;
13+
14+
/**
15+
* @author Kévin Dunglas <dunglas@gmail.com>
16+
*/
17+
class NotInstantiable
18+
{
19+
private function __construct(string $foo)
20+
{
21+
}
22+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.