feature #26300 [PropertyInfo] Implement "Collection" types in PhpDocExtractor (popy-dev)
This PR was squashed before being merged into the 4.2-dev branch (closes #26300).
Discussion
----------
[PropertyInfo] Implement "Collection" types in PhpDocExtractor
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #26299
| License | MIT
| Doc PR | todo
Here's a proposition of implementation of my feature request #26299 :
I added few tests covering the requested feature, and I had to change a few things in the ```Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper``` class (createType no longer gets a string, but a ```phpDocumentor\Reflection\Type``` instance) to be able to detect properly Collections and their subtypes.
Of course a simpler implementation is possible, without changing the PhpDocTypeHelper internal behaviour, by matching the input string against ```/^([^>]+)</``` and extracting the classname alone.
Commits
-------
12bafe46d0
[PropertyInfo] Implement \"Collection\" types in PhpDocExtractor
This commit is contained in:
commit
1a3d445ec5
@ -14,6 +14,7 @@ namespace Symfony\Component\PropertyInfo\Tests\PhpDocExtractor;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
|
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
|
||||||
use Symfony\Component\PropertyInfo\Type;
|
use Symfony\Component\PropertyInfo\Type;
|
||||||
|
use phpDocumentor\Reflection\Types\Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
@ -77,6 +78,39 @@ class PhpDocExtractorTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider provideCollectionTypes
|
||||||
|
*/
|
||||||
|
public function testExtractCollection($property, array $type = null, $shortDescription, $longDescription)
|
||||||
|
{
|
||||||
|
if (!class_exists(Collection::class)) {
|
||||||
|
$this->markTestSkipped('Collections are not implemented in current phpdocumentor/type-resolver version');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->testExtract($property, $type, $shortDescription, $longDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideCollectionTypes()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('iteratorCollection', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, null, new Type(Type::BUILTIN_TYPE_STRING))), null, null),
|
||||||
|
array('iteratorCollectionWithKey', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))), null, null),
|
||||||
|
array(
|
||||||
|
'nestedIterators',
|
||||||
|
array(new Type(
|
||||||
|
Type::BUILTIN_TYPE_OBJECT,
|
||||||
|
false,
|
||||||
|
'Iterator',
|
||||||
|
true,
|
||||||
|
new Type(Type::BUILTIN_TYPE_INT),
|
||||||
|
new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Iterator', true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING))
|
||||||
|
)),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testParamTagTypeIsOmitted()
|
public function testParamTagTypeIsOmitted()
|
||||||
{
|
{
|
||||||
$this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType'));
|
$this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType'));
|
||||||
|
@ -42,6 +42,9 @@ class ReflectionExtractorTest extends TestCase
|
|||||||
'Guid',
|
'Guid',
|
||||||
'array',
|
'array',
|
||||||
'emptyVar',
|
'emptyVar',
|
||||||
|
'iteratorCollection',
|
||||||
|
'iteratorCollectionWithKey',
|
||||||
|
'nestedIterators',
|
||||||
'foo',
|
'foo',
|
||||||
'foo2',
|
'foo2',
|
||||||
'foo3',
|
'foo3',
|
||||||
@ -79,6 +82,9 @@ class ReflectionExtractorTest extends TestCase
|
|||||||
'Guid',
|
'Guid',
|
||||||
'array',
|
'array',
|
||||||
'emptyVar',
|
'emptyVar',
|
||||||
|
'iteratorCollection',
|
||||||
|
'iteratorCollectionWithKey',
|
||||||
|
'nestedIterators',
|
||||||
'foo',
|
'foo',
|
||||||
'foo2',
|
'foo2',
|
||||||
'foo3',
|
'foo3',
|
||||||
@ -107,6 +113,9 @@ class ReflectionExtractorTest extends TestCase
|
|||||||
'Guid',
|
'Guid',
|
||||||
'array',
|
'array',
|
||||||
'emptyVar',
|
'emptyVar',
|
||||||
|
'iteratorCollection',
|
||||||
|
'iteratorCollectionWithKey',
|
||||||
|
'nestedIterators',
|
||||||
'foo',
|
'foo',
|
||||||
'foo2',
|
'foo2',
|
||||||
'foo3',
|
'foo3',
|
||||||
|
@ -75,6 +75,21 @@ class Dummy extends ParentDummy
|
|||||||
*/
|
*/
|
||||||
public $emptyVar;
|
public $emptyVar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Iterator<string>
|
||||||
|
*/
|
||||||
|
public $iteratorCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Iterator<integer,string>
|
||||||
|
*/
|
||||||
|
public $iteratorCollectionWithKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Iterator<integer,\Iterator<integer,string>>
|
||||||
|
*/
|
||||||
|
public $nestedIterators;
|
||||||
|
|
||||||
public static function getStatic()
|
public static function getStatic()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Component\PropertyInfo\Util;
|
namespace Symfony\Component\PropertyInfo\Util;
|
||||||
|
|
||||||
use phpDocumentor\Reflection\Type as DocType;
|
use phpDocumentor\Reflection\Type as DocType;
|
||||||
|
use phpDocumentor\Reflection\Types\Collection;
|
||||||
use phpDocumentor\Reflection\Types\Compound;
|
use phpDocumentor\Reflection\Types\Compound;
|
||||||
use phpDocumentor\Reflection\Types\Null_;
|
use phpDocumentor\Reflection\Types\Null_;
|
||||||
use Symfony\Component\PropertyInfo\Type;
|
use Symfony\Component\PropertyInfo\Type;
|
||||||
@ -39,7 +40,7 @@ final class PhpDocTypeHelper
|
|||||||
$nullable = true;
|
$nullable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$type = $this->createType((string) $varType, $nullable);
|
$type = $this->createType($varType, $nullable);
|
||||||
if (null !== $type) {
|
if (null !== $type) {
|
||||||
$types[] = $type;
|
$types[] = $type;
|
||||||
}
|
}
|
||||||
@ -49,16 +50,15 @@ final class PhpDocTypeHelper
|
|||||||
|
|
||||||
$varTypes = array();
|
$varTypes = array();
|
||||||
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
|
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
|
||||||
$varTypes[] = (string) $varType->get($typeIndex);
|
$type = $varType->get($typeIndex);
|
||||||
}
|
|
||||||
|
|
||||||
// If null is present, all types are nullable
|
// If null is present, all types are nullable
|
||||||
$nullKey = array_search(Type::BUILTIN_TYPE_NULL, $varTypes);
|
if ($type instanceof Null_) {
|
||||||
$nullable = false !== $nullKey;
|
$nullable = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the null type from the type if other types are defined
|
$varTypes[] = $type;
|
||||||
if ($nullable && count($varTypes) > 1) {
|
|
||||||
unset($varTypes[$nullKey]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($varTypes as $varType) {
|
foreach ($varTypes as $varType) {
|
||||||
@ -74,8 +74,24 @@ final class PhpDocTypeHelper
|
|||||||
/**
|
/**
|
||||||
* Creates a {@see Type} from a PHPDoc type.
|
* Creates a {@see Type} from a PHPDoc type.
|
||||||
*/
|
*/
|
||||||
private function createType(string $docType, bool $nullable): ?Type
|
private function createType(DocType $type, bool $nullable): ?Type
|
||||||
{
|
{
|
||||||
|
$docType = (string) $type;
|
||||||
|
|
||||||
|
if ($type instanceof Collection) {
|
||||||
|
list($phpType, $class) = $this->getPhpTypeAndClass((string) $type->getFqsen());
|
||||||
|
|
||||||
|
$key = $this->getTypes($type->getKeyType());
|
||||||
|
$value = $this->getTypes($type->getValueType());
|
||||||
|
|
||||||
|
// More than 1 type returned means it is a Compound type, which is
|
||||||
|
// not handled by Type, so better use a null value.
|
||||||
|
$key = 1 === \count($key) ? $key[0] : null;
|
||||||
|
$value = 1 === \count($value) ? $value[0] : null;
|
||||||
|
|
||||||
|
return new Type($phpType, $nullable, $class, true, $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
// Cannot guess
|
// Cannot guess
|
||||||
if (!$docType || 'mixed' === $docType) {
|
if (!$docType || 'mixed' === $docType) {
|
||||||
return null;
|
return null;
|
||||||
|
Reference in New Issue
Block a user