-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[PropertyInfo] ConstructorExtractor which has higher priority than PhpDocExtractor and ReflectionExtractor #30128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* 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 <[email protected]> | ||
*/ | ||
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)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* 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\Type; | ||
|
||
/** | ||
* Infers the constructor argument type. | ||
* | ||
* @author Dmitrii Poddubnyi <[email protected]> | ||
*/ | ||
interface ConstructorArgumentTypeExtractorInterface | ||
{ | ||
/** | ||
* Gets types of an argument from constructor. | ||
* | ||
* @param string $class | ||
* @param string $property | ||
* | ||
* @return Type[]|null | ||
*/ | ||
public function getTypesFromConstructor($class, $property); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <[email protected]> | ||
* | ||
* 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 <[email protected]> | ||
*/ | ||
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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,7 +27,7 @@ | |
* | ||
* @final | ||
*/ | ||
class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface | ||
class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, ConstructorArgumentTypeExtractorInterface | ||
{ | ||
const PROPERTY = 0; | ||
const ACCESSOR = 1; | ||
|
@@ -151,6 +151,77 @@ 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 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should use type hints and return types wherever possible as this is for 4.2 and new code if I am right There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @OskarStark I applied the type hints in #30335 |
||
{ | ||
$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()); | ||
} | ||
|
||
private function getDocBlock(string $class, string $property): array | ||
{ | ||
$propertyHash = sprintf('%s::%s', $class, $property); | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -25,7 +25,7 @@ | |||||
* | ||||||
* @final | ||||||
*/ | ||||||
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface | ||||||
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface, PropertyInitializableExtractorInterface, ConstructorArgumentTypeExtractorInterface | ||||||
{ | ||||||
/** | ||||||
* @internal | ||||||
|
@@ -119,6 +119,50 @@ 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 (!$reflectionType = $reflectionParameter->getType()) { | ||||||
return null; | ||||||
} | ||||||
if (!$type = $this->extractFromReflectionType($reflectionType, $reflectionConstructor)) { | ||||||
return null; | ||||||
} | ||||||
|
||||||
return [$type]; | ||||||
} | ||||||
|
||||||
/** | ||||||
* @param string $property | ||||||
* @param \ReflectionMethod $reflectionConstructor | ||||||
* | ||||||
* @return \ReflectionParameter|null | ||||||
*/ | ||||||
private function getReflectionParameterFromConstructor($property, \ReflectionMethod $reflectionConstructor) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
{ | ||||||
$reflectionParameter = null; | ||||||
foreach ($reflectionConstructor->getParameters() as $reflectionParameter) { | ||||||
if ($reflectionParameter->getName() === $property) { | ||||||
return $reflectionParameter; | ||||||
} | ||||||
} | ||||||
|
||||||
return null; | ||||||
} | ||||||
|
||||||
/** | ||||||
* {@inheritdoc} | ||||||
*/ | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should add typehints and return type