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

Skip to content

Commit 29586b4

Browse files
committed
Added ConstructorExtractor which has higher priority than PhpDocExtractor and ReflectionExtractor
1 parent 38d5528 commit 29586b4

15 files changed

+472
-11
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,6 +1583,7 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container,
15831583
$definition->setPrivate(true);
15841584
$definition->addTag('property_info.description_extractor', ['priority' => -1000]);
15851585
$definition->addTag('property_info.type_extractor', ['priority' => -1001]);
1586+
$definition->addTag('property_info.constructor_extractor', ['priority' => -1001]);
15861587
}
15871588
}
15881589

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
4343
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
4444
use Symfony\Component\HttpKernel\KernelEvents;
45+
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoConstructorPass;
4546
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass;
4647
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
4748
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
@@ -116,6 +117,7 @@ public function build(ContainerBuilder $container)
116117
$container->addCompilerPass(new FragmentRendererPass());
117118
$this->addCompilerPassIfExists($container, SerializerPass::class);
118119
$this->addCompilerPassIfExists($container, PropertyInfoPass::class);
120+
$this->addCompilerPassIfExists($container, PropertyInfoConstructorPass::class);
119121
$container->addCompilerPass(new DataCollectorTranslatorPass());
120122
$container->addCompilerPass(new ControllerArgumentValueResolverPass());
121123
$container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32);

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
<service id="property_info.reflection_extractor" class="Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor">
2020
<tag name="property_info.list_extractor" priority="-1000" />
2121
<tag name="property_info.type_extractor" priority="-1002" />
22+
<tag name="property_info.constructor_extractor" priority="-1002" />
2223
<tag name="property_info.access_extractor" priority="-1000" />
2324
</service>
25+
26+
<service id="property_info.constructor_extractor" class="Symfony\Component\PropertyInfo\Extractor\ConstructorExtractor">
27+
<argument type="collection" />
28+
<tag name="property_info.type_extractor" priority="-999" />
29+
</service>
2430
</services>
2531
</container>

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
1313

14+
use Symfony\Component\DependencyInjection\ContainerInterface;
1415
use Symfony\Component\PropertyInfo\Type;
1516

1617
class PropertyInfoTest extends WebTestCase
@@ -22,6 +23,29 @@ public function testPhpDocPriority()
2223

2324
$this->assertEquals([new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT))], $container->get('test.property_info')->getTypes('Symfony\Bundle\FrameworkBundle\Tests\Functional\Dummy', 'codes'));
2425
}
26+
27+
/**
28+
* @dataProvider constructorOverridesPropertyTypeProvider
29+
*/
30+
public function testConstructorOverridesPropertyType(ContainerInterface $container, $property, array $type = null)
31+
{
32+
$extractor = $container->get('test.property_info');
33+
$this->assertEquals($type, $extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
34+
}
35+
36+
public function constructorOverridesPropertyTypeProvider()
37+
{
38+
static::bootKernel(['test_case' => 'Serializer']);
39+
$c = static::$kernel->getContainer();
40+
41+
return [
42+
[$c, 'timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]],
43+
[$c, 'date', [new Type(Type::BUILTIN_TYPE_INT)]],
44+
[$c, 'dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]],
45+
[$c, 'dateTime', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')]],
46+
[$c, 'ddd', null],
47+
];
48+
}
2549
}
2650

2751
class Dummy
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
15+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16+
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
19+
/**
20+
* Adds extractors to the property_info.constructor_extractor service.
21+
*
22+
* @author Dmitrii Poddubnyi <[email protected]>
23+
*/
24+
class PropertyInfoConstructorPass implements CompilerPassInterface
25+
{
26+
use PriorityTaggedServiceTrait;
27+
28+
private $service;
29+
private $tag;
30+
31+
public function __construct($service = 'property_info.constructor_extractor', $tag = 'property_info.constructor_extractor')
32+
{
33+
$this->service = $service;
34+
$this->tag = $tag;
35+
}
36+
37+
/**
38+
* {@inheritdoc}
39+
*/
40+
public function process(ContainerBuilder $container)
41+
{
42+
if (!$container->hasDefinition($this->service)) {
43+
return;
44+
}
45+
$definition = $container->getDefinition($this->service);
46+
47+
$listExtractors = $this->findAndSortTaggedServices($this->tag, $container);
48+
$definition->replaceArgument(0, new IteratorArgument($listExtractors));
49+
}
50+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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\Extractor;
13+
14+
/**
15+
* Infers the constructor argument type.
16+
*
17+
* @author Dmitrii Poddubnyi <[email protected]>
18+
*/
19+
interface ConstructorArgumentTypeExtractorInterface
20+
{
21+
/**
22+
* Gets types of an argument from constructor.
23+
*
24+
* @param string $class
25+
* @param string $property
26+
*
27+
* @return Type[]|null
28+
*/
29+
public function getTypesFromConstructor($class, $property);
30+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\Extractor;
13+
14+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
15+
16+
/**
17+
* Extracts the constructor argument type using ConstructorArgumentTypeExtractorInterface implementations.
18+
*
19+
* @author Dmitrii Poddubnyi <[email protected]>
20+
*/
21+
class ConstructorExtractor implements PropertyTypeExtractorInterface
22+
{
23+
/** @var iterable|ConstructorArgumentTypeExtractorInterface[] */
24+
private $extractors;
25+
26+
/**
27+
* @param iterable|ConstructorArgumentTypeExtractorInterface[] $extractors
28+
*/
29+
public function __construct($extractors = [])
30+
{
31+
$this->extractors = $extractors;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function getTypes($class, $property, array $context = [])
38+
{
39+
foreach ($this->extractors as $extractor) {
40+
$value = $extractor->getTypesFromConstructor($class, $property);
41+
if (null !== $value) {
42+
return $value;
43+
}
44+
}
45+
46+
return null;
47+
}
48+
}

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

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
*
2828
* @final since version 3.3
2929
*/
30-
class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface
30+
class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface
3131
{
3232
const PROPERTY = 0;
3333
const ACCESSOR = 1;
@@ -151,6 +151,32 @@ public function getTypes($class, $property, array $context = [])
151151
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])];
152152
}
153153

154+
/**
155+
* {@inheritdoc}
156+
*/
157+
public function getTypesFromConstructor($class, $property)
158+
{
159+
$docBlock = $this->getDocBlockFromConstructor($class, $property);
160+
161+
if (!$docBlock) {
162+
return;
163+
}
164+
165+
$types = [];
166+
/** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */
167+
foreach ($docBlock->getTagsByName('param') as $tag) {
168+
if ($tag && null !== $tag->getType()) {
169+
$types = array_merge($types, $this->phpDocTypeHelper->getTypes($tag->getType()));
170+
}
171+
}
172+
173+
if (!isset($types[0])) {
174+
return;
175+
}
176+
177+
return $types;
178+
}
179+
154180
/**
155181
* Gets the DocBlock for this property.
156182
*
@@ -189,6 +215,51 @@ private function getDocBlock($class, $property)
189215
return $this->docBlocks[$propertyHash] = $data;
190216
}
191217

218+
/**
219+
* Gets the DocBlock from a constructor.
220+
*
221+
* @param string $class
222+
* @param string $property
223+
*
224+
* @return DocBlock|null
225+
*/
226+
private function getDocBlockFromConstructor($class, $property)
227+
{
228+
try {
229+
$reflectionClass = new \ReflectionClass($class);
230+
} catch (\ReflectionException $e) {
231+
return null;
232+
}
233+
$reflectionConstructor = $reflectionClass->getConstructor();
234+
if (!$reflectionConstructor) {
235+
return null;
236+
}
237+
238+
try {
239+
$docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor));
240+
241+
return $this->filterDocBlockParams($docBlock, $property);
242+
} catch (\InvalidArgumentException $e) {
243+
return null;
244+
}
245+
}
246+
247+
/**
248+
* @param DocBlock $docBlock
249+
* @param string $allowedParam
250+
*
251+
* @return DocBlock
252+
*/
253+
private function filterDocBlockParams(DocBlock $docBlock, $allowedParam)
254+
{
255+
$tags = array_values(array_filter($docBlock->getTagsByName('param'), function ($tag) use ($allowedParam) {
256+
return $tag instanceof DocBlock\Tags\Param && $allowedParam === $tag->getVariableName();
257+
}));
258+
259+
return new DocBlock($docBlock->getSummary(), $docBlock->getDescription(), $tags, $docBlock->getContext(),
260+
$docBlock->getLocation(), $docBlock->isTemplateStart(), $docBlock->isTemplateEnd());
261+
}
262+
192263
/**
193264
* Gets the DocBlock from a property.
194265
*

0 commit comments

Comments
 (0)