diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index a46e552a255e3..c32b347b5be82 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -1583,6 +1583,7 @@ private function registerPropertyInfoConfiguration(ContainerBuilder $container,
$definition->setPrivate(true);
$definition->addTag('property_info.description_extractor', ['priority' => -1000]);
$definition->addTag('property_info.type_extractor', ['priority' => -1001]);
+ $definition->addTag('property_info.constructor_extractor', ['priority' => -1001]);
}
}
diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
index daf7a37bf8547..17b4645774905 100644
--- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -42,6 +42,7 @@
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoConstructorPass;
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass;
use Symfony\Component\Routing\DependencyInjection\RoutingResolverPass;
use Symfony\Component\Serializer\DependencyInjection\SerializerPass;
@@ -116,6 +117,7 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new FragmentRendererPass());
$this->addCompilerPassIfExists($container, SerializerPass::class);
$this->addCompilerPassIfExists($container, PropertyInfoPass::class);
+ $this->addCompilerPassIfExists($container, PropertyInfoConstructorPass::class);
$container->addCompilerPass(new DataCollectorTranslatorPass());
$container->addCompilerPass(new ControllerArgumentValueResolverPass());
$container->addCompilerPass(new CachePoolPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 32);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml
index a893127276564..6dbe61ee2223a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/property_info.xml
@@ -19,7 +19,13 @@
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php
index 832b45b818f76..6b74f9771024f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/PropertyInfoTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
+use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\PropertyInfo\Type;
class PropertyInfoTest extends WebTestCase
@@ -22,6 +23,29 @@ public function testPhpDocPriority()
$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'));
}
+
+ /**
+ * @dataProvider constructorOverridesPropertyTypeProvider
+ */
+ public function testConstructorOverridesPropertyType(ContainerInterface $container, $property, array $type = null)
+ {
+ $extractor = $container->get('test.property_info');
+ $this->assertEquals($type, $extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
+ }
+
+ public function constructorOverridesPropertyTypeProvider()
+ {
+ static::bootKernel(['test_case' => 'Serializer']);
+ $c = static::$kernel->getContainer();
+
+ return [
+ [$c, 'timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]],
+ [$c, 'date', [new Type(Type::BUILTIN_TYPE_INT)]],
+ [$c, 'dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]],
+ [$c, 'dateTime', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')]],
+ [$c, 'ddd', null],
+ ];
+ }
}
class Dummy
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index c8ed98787e15e..a337d66628b29 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -52,7 +52,7 @@
"symfony/var-dumper": "~3.3|~4.0",
"symfony/workflow": "~3.3|~4.0",
"symfony/yaml": "~3.2|~4.0",
- "symfony/property-info": "~3.3|~4.0",
+ "symfony/property-info": "~3.4.23|^4.2.4",
"symfony/lock": "~3.4|~4.0",
"symfony/web-link": "~3.3|~4.0",
"doctrine/annotations": "~1.0",
@@ -66,7 +66,7 @@
"symfony/asset": "<3.3",
"symfony/console": "<3.4",
"symfony/form": "<3.4",
- "symfony/property-info": "<3.3",
+ "symfony/property-info": "<3.4.23|>=4.0.0,<4.2.4",
"symfony/serializer": "<3.3",
"symfony/stopwatch": "<3.4",
"symfony/translation": "<3.4",
diff --git a/src/Symfony/Component/PropertyInfo/DependencyInjection/PropertyInfoConstructorPass.php b/src/Symfony/Component/PropertyInfo/DependencyInjection/PropertyInfoConstructorPass.php
new file mode 100644
index 0000000000000..ccf84e82faa3c
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/DependencyInjection/PropertyInfoConstructorPass.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyInfo\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Adds extractors to the property_info.constructor_extractor service.
+ *
+ * @author Dmitrii Poddubnyi
+ */
+class PropertyInfoConstructorPass implements CompilerPassInterface
+{
+ use PriorityTaggedServiceTrait;
+
+ private $service;
+ private $tag;
+
+ public function __construct($service = 'property_info.constructor_extractor', $tag = 'property_info.constructor_extractor')
+ {
+ $this->service = $service;
+ $this->tag = $tag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition($this->service)) {
+ return;
+ }
+ $definition = $container->getDefinition($this->service);
+
+ $listExtractors = $this->findAndSortTaggedServices($this->tag, $container);
+ $definition->replaceArgument(0, new IteratorArgument($listExtractors));
+ }
+}
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php
new file mode 100644
index 0000000000000..b75526cb2f8fe
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorArgumentTypeExtractorInterface.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyInfo\Extractor;
+
+/**
+ * Infers the constructor argument type.
+ *
+ * @author Dmitrii Poddubnyi
+ */
+interface ConstructorArgumentTypeExtractorInterface
+{
+ /**
+ * Gets types of an argument from constructor.
+ *
+ * @param string $class
+ * @param string $property
+ *
+ * @return Type[]|null
+ */
+ public function getTypesFromConstructor($class, $property);
+}
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ConstructorExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorExtractor.php
new file mode 100644
index 0000000000000..9695523bc050a
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Extractor/ConstructorExtractor.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyInfo\Extractor;
+
+use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
+
+/**
+ * Extracts the constructor argument type using ConstructorArgumentTypeExtractorInterface implementations.
+ *
+ * @author Dmitrii Poddubnyi
+ */
+class ConstructorExtractor implements PropertyTypeExtractorInterface
+{
+ /** @var iterable|ConstructorArgumentTypeExtractorInterface[] */
+ private $extractors;
+
+ /**
+ * @param iterable|ConstructorArgumentTypeExtractorInterface[] $extractors
+ */
+ public function __construct($extractors = [])
+ {
+ $this->extractors = $extractors;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTypes($class, $property, array $context = [])
+ {
+ foreach ($this->extractors as $extractor) {
+ $value = $extractor->getTypesFromConstructor($class, $property);
+ if (null !== $value) {
+ return $value;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php
index 7d47cd047003f..6e94fa4dc3f2d 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php
@@ -27,7 +27,7 @@
*
* @final since version 3.3
*/
-class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface
+class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface
{
const PROPERTY = 0;
const ACCESSOR = 1;
@@ -151,6 +151,32 @@ public function getTypes($class, $property, array $context = [])
return [new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0])];
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getTypesFromConstructor($class, $property)
+ {
+ $docBlock = $this->getDocBlockFromConstructor($class, $property);
+
+ if (!$docBlock) {
+ return;
+ }
+
+ $types = [];
+ /** @var DocBlock\Tags\Var_|DocBlock\Tags\Return_|DocBlock\Tags\Param $tag */
+ foreach ($docBlock->getTagsByName('param') as $tag) {
+ if ($tag && null !== $tag->getType()) {
+ $types = array_merge($types, $this->phpDocTypeHelper->getTypes($tag->getType()));
+ }
+ }
+
+ if (!isset($types[0])) {
+ return;
+ }
+
+ return $types;
+ }
+
/**
* Gets the DocBlock for this property.
*
@@ -189,6 +215,51 @@ private function getDocBlock($class, $property)
return $this->docBlocks[$propertyHash] = $data;
}
+ /**
+ * Gets the DocBlock from a constructor.
+ *
+ * @param string $class
+ * @param string $property
+ *
+ * @return DocBlock|null
+ */
+ private function getDocBlockFromConstructor($class, $property)
+ {
+ try {
+ $reflectionClass = new \ReflectionClass($class);
+ } catch (\ReflectionException $e) {
+ return null;
+ }
+ $reflectionConstructor = $reflectionClass->getConstructor();
+ if (!$reflectionConstructor) {
+ return null;
+ }
+
+ try {
+ $docBlock = $this->docBlockFactory->create($reflectionConstructor, $this->contextFactory->createFromReflector($reflectionConstructor));
+
+ return $this->filterDocBlockParams($docBlock, $property);
+ } catch (\InvalidArgumentException $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @param DocBlock $docBlock
+ * @param string $allowedParam
+ *
+ * @return DocBlock
+ */
+ private function filterDocBlockParams(DocBlock $docBlock, $allowedParam)
+ {
+ $tags = array_values(array_filter($docBlock->getTagsByName('param'), function ($tag) use ($allowedParam) {
+ return $tag instanceof DocBlock\Tags\Param && $allowedParam === $tag->getVariableName();
+ }));
+
+ return new DocBlock($docBlock->getSummary(), $docBlock->getDescription(), $tags, $docBlock->getContext(),
+ $docBlock->getLocation(), $docBlock->isTemplateStart(), $docBlock->isTemplateEnd());
+ }
+
/**
* Gets the DocBlock from a property.
*
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
index 513549219f21e..7dc6bcc7fb92d 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php
@@ -24,7 +24,7 @@
*
* @final since version 3.3
*/
-class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
+class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, ConstructorArgumentTypeExtractorInterface
{
/**
* @internal
@@ -126,6 +126,47 @@ public function getTypes($class, $property, array $context = [])
}
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getTypesFromConstructor($class, $property)
+ {
+ try {
+ $reflection = new \ReflectionClass($class);
+ } catch (\ReflectionException $e) {
+ return null;
+ }
+ if (!$reflectionConstructor = $reflection->getConstructor()) {
+ return null;
+ }
+ if (!$reflectionParameter = $this->getReflectionParameterFromConstructor($property, $reflectionConstructor)) {
+ return null;
+ }
+ if (!$type = $this->extractFromReflectionMethod($reflectionParameter, $reflectionConstructor)) {
+ return null;
+ }
+
+ return [$type];
+ }
+
+ /**
+ * @param string $property
+ * @param \ReflectionMethod $reflectionConstructor
+ *
+ * @return \ReflectionParameter|null
+ */
+ private function getReflectionParameterFromConstructor($property, \ReflectionMethod $reflectionConstructor)
+ {
+ $reflectionParameter = null;
+ foreach ($reflectionConstructor->getParameters() as $reflectionParameter) {
+ if ($reflectionParameter->getName() === $property) {
+ return $reflectionParameter;
+ }
+ }
+
+ return null;
+ }
+
/**
* {@inheritdoc}
*/
@@ -172,16 +213,37 @@ private function extractFromMutator($class, $property)
$reflectionParameters = $reflectionMethod->getParameters();
$reflectionParameter = $reflectionParameters[0];
+ if (!$type = $this->extractFromReflectionMethod($reflectionParameter, $reflectionMethod)) {
+ return null;
+ }
+
+ if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
+ $type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type);
+ }
+
+ return [$type];
+ }
+
+ /**
+ * @param \ReflectionParameter $reflectionParameter
+ * @param \ReflectionMethod $reflectionMethod
+ *
+ * @return Type|null
+ */
+ private function extractFromReflectionMethod(\ReflectionParameter $reflectionParameter, \ReflectionMethod $reflectionMethod)
+ {
if ($this->supportsParameterType) {
if (!$reflectionType = $reflectionParameter->getType()) {
- return;
+ return null;
}
$type = $this->extractFromReflectionType($reflectionType, $reflectionMethod);
// HHVM reports variadics with "array" but not builtin type hints
if (!$reflectionType->isBuiltin() && Type::BUILTIN_TYPE_ARRAY === $type->getBuiltinType()) {
- return;
+ return null;
}
+
+ return $type;
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $reflectionParameter, $info)) {
if (Type::BUILTIN_TYPE_ARRAY === $info[1]) {
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $reflectionParameter->allowsNull(), null, true);
@@ -190,15 +252,11 @@ private function extractFromMutator($class, $property)
} else {
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $reflectionParameter->allowsNull(), $this->resolveTypeName($info[1], $reflectionMethod));
}
- } else {
- return;
- }
- if (\in_array($prefix, $this->arrayMutatorPrefixes)) {
- $type = new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $type);
+ return $type;
}
- return [$type];
+ return null;
}
/**
diff --git a/src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoConstructorPassTest.php b/src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoConstructorPassTest.php
new file mode 100644
index 0000000000000..ee3151f2710a9
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Tests/DependencyInjection/PropertyInfoConstructorPassTest.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\PropertyInfo\Tests\DependencyInjection;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoConstructorPass;
+
+class PropertyInfoConstructorPassTest extends TestCase
+{
+ public function testServicesAreOrderedAccordingToPriority()
+ {
+ $container = new ContainerBuilder();
+
+ $tag = 'property_info.constructor_extractor';
+ $definition = $container->register('property_info.constructor_extractor')->setArguments([null, null]);
+ $container->register('n2')->addTag($tag, ['priority' => 100]);
+ $container->register('n1')->addTag($tag, ['priority' => 200]);
+ $container->register('n3')->addTag($tag);
+
+ $pass = new PropertyInfoConstructorPass();
+ $pass->process($container);
+
+ $expected = new IteratorArgument([
+ new Reference('n1'),
+ new Reference('n2'),
+ new Reference('n3'),
+ ]);
+ $this->assertEquals($expected, $definition->getArgument(0));
+ }
+
+ public function testReturningEmptyArrayWhenNoService()
+ {
+ $container = new ContainerBuilder();
+ $propertyInfoExtractorDefinition = $container->register('property_info.constructor_extractor')
+ ->setArguments([[]]);
+
+ $pass = new PropertyInfoConstructorPass();
+ $pass->process($container);
+
+ $this->assertEquals(new IteratorArgument([]), $propertyInfoExtractorDefinition->getArgument(0));
+ }
+}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ConstructorExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ConstructorExtractorTest.php
new file mode 100644
index 0000000000000..11a28c0f7c824
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ConstructorExtractorTest.php
@@ -0,0 +1,40 @@
+
+ */
+class ConstructorExtractorTest extends TestCase
+{
+ /**
+ * @var ConstructorExtractor
+ */
+ private $extractor;
+
+ protected function setUp()
+ {
+ $this->extractor = new ConstructorExtractor([new DummyExtractor()]);
+ }
+
+ public function testInstanceOf()
+ {
+ $this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface', $this->extractor);
+ }
+
+ public function testGetTypes()
+ {
+ $this->assertEquals([new Type(Type::BUILTIN_TYPE_STRING)], $this->extractor->getTypes('Foo', 'bar', []));
+ }
+
+ public function testGetTypes_ifNoExtractors()
+ {
+ $extractor = new ConstructorExtractor([]);
+ $this->assertNull($extractor->getTypes('Foo', 'bar', []));
+ }
+}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
index f4f9e9dc77e29..0d093b0b6be51 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/PhpDocExtractorTest.php
@@ -213,6 +213,25 @@ public function testDocBlockFallback($property, $types)
{
$this->assertEquals($types, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\DockBlockFallback', $property));
}
+
+ /**
+ * @dataProvider constructorTypesProvider
+ */
+ public function testExtractConstructorTypes($property, array $type = null)
+ {
+ $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
+ }
+
+ public function constructorTypesProvider()
+ {
+ return [
+ ['date', [new Type(Type::BUILTIN_TYPE_INT)]],
+ ['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]],
+ ['dateObject', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeInterface')]],
+ ['dateTime', null],
+ ['ddd', null],
+ ];
+ }
}
class EmptyDocBlock
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
index 0ed5390bb5882..68bca2dbbcbaa 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractor/ReflectionExtractorTest.php
@@ -263,4 +263,23 @@ public function testSingularize()
$this->assertTrue($this->extractor->isWritable(AdderRemoverDummy::class, 'feet'));
$this->assertEquals(['analyses', 'feet'], $this->extractor->getProperties(AdderRemoverDummy::class));
}
+
+ /**
+ * @dataProvider constructorTypesProvider
+ */
+ public function testExtractConstructorTypes($property, array $type = null)
+ {
+ $this->assertEquals($type, $this->extractor->getTypesFromConstructor('Symfony\Component\PropertyInfo\Tests\Fixtures\ConstructorDummy', $property));
+ }
+
+ public function constructorTypesProvider()
+ {
+ return [
+ ['timezone', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTimeZone')]],
+ ['date', null],
+ ['dateObject', null],
+ ['dateTime', [new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')]],
+ ['ddd', null],
+ ];
+ }
}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummy.php
new file mode 100644
index 0000000000000..23ef5cceaef75
--- /dev/null
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/ConstructorDummy.php
@@ -0,0 +1,30 @@
+
+ */
+class ConstructorDummy
+{
+ /** @var string */
+ private $timezone;
+
+ /** @var \DateTimeInterface */
+ private $date;
+
+ /** @var int */
+ private $dateTime;
+
+ /**
+ * @param \DateTimeZone $timezone
+ * @param int $date Timestamp
+ * @param \DateTimeInterface $dateObject
+ */
+ public function __construct(\DateTimeZone $timezone, $date, $dateObject, \DateTime $dateTime)
+ {
+ $this->timezone = $timezone->getName();
+ $this->date = \DateTime::createFromFormat('U', $date);
+ $this->dateTime = $dateTime->getTimestamp();
+ }
+}
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php
index cb17020a55eb4..88eda16311e31 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/DummyExtractor.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
+use Symfony\Component\PropertyInfo\Extractor\ConstructorArgumentTypeExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
@@ -20,7 +21,7 @@
/**
* @author Kévin Dunglas
*/
-class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
+class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, ConstructorArgumentTypeExtractorInterface
{
/**
* {@inheritdoc}
@@ -46,6 +47,14 @@ public function getTypes($class, $property, array $context = [])
return [new Type(Type::BUILTIN_TYPE_INT)];
}
+ /**
+ * {@inheritdoc}
+ */
+ public function getTypesFromConstructor($class, $property)
+ {
+ return [new Type(Type::BUILTIN_TYPE_STRING)];
+ }
+
/**
* {@inheritdoc}
*/