[PropertyInfo] Implement \"Collection\" types in PhpDocExtractor

This commit is contained in:
Pierre Dudoret 2018-02-24 18:21:57 +01:00 committed by Fabien Potencier
parent be1b37f017
commit 12bafe46d0
4 changed files with 84 additions and 10 deletions

View File

@ -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'));

View File

@ -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',

View File

@ -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()
{ {
} }

View File

@ -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;