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:
Fabien Potencier 2018-06-26 18:34:19 +02:00
commit 1a3d445ec5
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 Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Type;
use phpDocumentor\Reflection\Types\Collection;
/**
* @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()
{
$this->assertNull($this->extractor->getTypes(OmittedParamTagTypeDocBlock::class, 'omittedType'));

View File

@ -42,6 +42,9 @@ class ReflectionExtractorTest extends TestCase
'Guid',
'array',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
'nestedIterators',
'foo',
'foo2',
'foo3',
@ -79,6 +82,9 @@ class ReflectionExtractorTest extends TestCase
'Guid',
'array',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
'nestedIterators',
'foo',
'foo2',
'foo3',
@ -107,6 +113,9 @@ class ReflectionExtractorTest extends TestCase
'Guid',
'array',
'emptyVar',
'iteratorCollection',
'iteratorCollectionWithKey',
'nestedIterators',
'foo',
'foo2',
'foo3',

View File

@ -75,6 +75,21 @@ class Dummy extends ParentDummy
*/
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()
{
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\PropertyInfo\Util;
use phpDocumentor\Reflection\Type as DocType;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Null_;
use Symfony\Component\PropertyInfo\Type;
@ -39,7 +40,7 @@ final class PhpDocTypeHelper
$nullable = true;
}
$type = $this->createType((string) $varType, $nullable);
$type = $this->createType($varType, $nullable);
if (null !== $type) {
$types[] = $type;
}
@ -49,16 +50,15 @@ final class PhpDocTypeHelper
$varTypes = array();
for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
$varTypes[] = (string) $varType->get($typeIndex);
}
$type = $varType->get($typeIndex);
// If null is present, all types are nullable
$nullKey = array_search(Type::BUILTIN_TYPE_NULL, $varTypes);
$nullable = false !== $nullKey;
// If null is present, all types are nullable
if ($type instanceof Null_) {
$nullable = true;
continue;
}
// Remove the null type from the type if other types are defined
if ($nullable && count($varTypes) > 1) {
unset($varTypes[$nullKey]);
$varTypes[] = $type;
}
foreach ($varTypes as $varType) {
@ -74,8 +74,24 @@ final class PhpDocTypeHelper
/**
* 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
if (!$docType || 'mixed' === $docType) {
return null;