Skip to content

Commit 3d6ce9f

Browse files
committed
fix for multiple class-string classes
e.g.: `class-string<\Foo\Bar|\Foo\Lall>`
1 parent 93ebd00 commit 3d6ce9f

File tree

4 files changed

+157
-8
lines changed

4 files changed

+157
-8
lines changed

src/TypeResolver.php

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use InvalidArgumentException;
1818
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
1919
use phpDocumentor\Reflection\PseudoTypes\List_;
20+
use phpDocumentor\Reflection\Types\AggregatedType;
2021
use phpDocumentor\Reflection\Types\Array_;
2122
use phpDocumentor\Reflection\Types\ArrayKey;
2223
use phpDocumentor\Reflection\Types\ClassString;
@@ -36,6 +37,7 @@
3637
use function array_key_exists;
3738
use function array_pop;
3839
use function array_values;
40+
use function assert;
3941
use function class_exists;
4042
use function class_implements;
4143
use function count;
@@ -261,7 +263,9 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
261263
$classType = array_pop($types);
262264
if ($classType !== null) {
263265
if ((string) $classType === 'class-string') {
264-
$types[] = $this->resolveClassString($tokens, $context);
266+
foreach ($this->resolveClassString($tokens, $context) as $classStringType) {
267+
$types[] = $classStringType;
268+
}
265269
} elseif ((string) $classType === 'int') {
266270
$types[] = $this->resolveIntRange($tokens);
267271
} elseif ((string) $classType === 'interface-string') {
@@ -461,17 +465,41 @@ private function resolveTypedObject(string $type, ?Context $context = null): Obj
461465
* Resolves class string
462466
*
463467
* @param ArrayIterator<int, (string|null)> $tokens
468+
*
469+
* @return array<int, ClassString>
464470
*/
465-
private function resolveClassString(ArrayIterator $tokens, Context $context): Type
471+
private function resolveClassString(ArrayIterator $tokens, Context $context): array
466472
{
467473
$tokens->next();
468474

469475
$classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
470476

471-
if (!$classType instanceof Object_ || $classType->getFqsen() === null) {
472-
throw new RuntimeException(
473-
$classType . ' is not a class string'
474-
);
477+
$aggreatedObjects = false;
478+
if ($classType instanceof AggregatedType) {
479+
foreach ($classType->getIterator() as $typeInner) {
480+
if (!$typeInner instanceof Object_) {
481+
break;
482+
}
483+
}
484+
485+
$aggreatedObjects = true;
486+
}
487+
488+
if ($aggreatedObjects) {
489+
assert($classType instanceof AggregatedType);
490+
foreach ($classType->getIterator() as $typeInner) {
491+
if (!$typeInner instanceof Object_ || $typeInner->getFqsen() === null) {
492+
throw new RuntimeException(
493+
$typeInner . ' is not a class string'
494+
);
495+
}
496+
}
497+
} else {
498+
if (!$classType instanceof Object_ || $classType->getFqsen() === null) {
499+
throw new RuntimeException(
500+
$classType . ' is not a class string'
501+
);
502+
}
475503
}
476504

477505
$token = $tokens->current();
@@ -487,7 +515,19 @@ private function resolveClassString(ArrayIterator $tokens, Context $context): Ty
487515
);
488516
}
489517

490-
return new ClassString($classType->getFqsen());
518+
$return = [];
519+
if ($aggreatedObjects) {
520+
assert($classType instanceof AggregatedType);
521+
foreach ($classType->getIterator() as $typeInner) {
522+
assert($typeInner instanceof Object_);
523+
$return[] = new ClassString($typeInner->getFqsen());
524+
}
525+
} else {
526+
assert($classType instanceof Object_);
527+
$return[] = new ClassString($classType->getFqsen());
528+
}
529+
530+
return $return;
491531
}
492532

493533
/**

src/Types/AbstractList.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
use phpDocumentor\Reflection\Type;
1717

18+
use function count;
19+
use function implode;
20+
1821
/**
1922
* Represents a list of values. This is an abstract class for Array_ and Collection.
2023
*
@@ -67,6 +70,30 @@ public function getValueType(): Type
6770
public function __toString(): string
6871
{
6972
if ($this->keyType) {
73+
$classStringValues = [];
74+
if ($this->valueType instanceof AggregatedType) {
75+
/**
76+
* @psalm-suppress ImpureMethodCall
77+
*/
78+
foreach ($this->valueType->getIterator() as $typeInner) {
79+
if (!($typeInner instanceof ClassString)) {
80+
$classStringValues = [];
81+
break;
82+
}
83+
84+
$fqsenTmp = $typeInner->getFqsen();
85+
if (!$fqsenTmp) {
86+
continue;
87+
}
88+
89+
$classStringValues[] = $fqsenTmp->__toString();
90+
}
91+
}
92+
93+
if (count($classStringValues) > 0) {
94+
return 'array<' . $this->keyType . ',class-string<' . implode('|', $classStringValues) . '>>';
95+
}
96+
7097
return 'array<' . $this->keyType . ',' . $this->valueType . '>';
7198
}
7299

src/Types/AggregatedType.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ public function has(int $index): bool
7777
public function contains(Type $type): bool
7878
{
7979
foreach ($this->types as $typePart) {
80-
// if the type is duplicate; do not add it
8180
if ((string) $typePart === (string) $type) {
8281
return true;
8382
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link http://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Reflection;
15+
16+
use phpDocumentor\Reflection\Types\Array_;
17+
use phpDocumentor\Reflection\Types\ClassString;
18+
use phpDocumentor\Reflection\Types\Compound;
19+
use phpDocumentor\Reflection\Types\Context;
20+
use phpDocumentor\Reflection\Types\Integer;
21+
use PHPUnit\Framework\TestCase;
22+
23+
/**
24+
* @covers ::<private>
25+
* @coversDefaultClass \phpDocumentor\Reflection\TypeResolver
26+
*/
27+
class ClassStringResolverTest extends TestCase
28+
{
29+
/**
30+
* @uses \phpDocumentor\Reflection\Types\Array_
31+
* @uses \phpDocumentor\Reflection\Types\Integer
32+
* @uses \phpDocumentor\Reflection\Types\ClassString
33+
*
34+
* @covers ::__construct
35+
* @covers ::resolve
36+
*/
37+
public function testResolvingClassString(): void
38+
{
39+
$fixture = new TypeResolver();
40+
41+
$resolvedType = $fixture->resolve('array<int,class-string<\Foo\Bar>>', new Context(''));
42+
43+
$this->assertInstanceOf(Array_::class, $resolvedType);
44+
$this->assertSame('array<int,class-string<\Foo\Bar>>', (string) $resolvedType);
45+
46+
$keyType = $resolvedType->getKeyType();
47+
$valueTpye = $resolvedType->getValueType();
48+
49+
$this->assertInstanceOf(Integer::class, $keyType);
50+
51+
$this->assertInstanceOf(ClassString::class, $valueTpye);
52+
$this->assertSame('Bar', $valueTpye->getFqsen()->getName());
53+
$this->assertSame('\Foo\Bar', $valueTpye->getFqsen()->__toString());
54+
}
55+
56+
/**
57+
* @uses \phpDocumentor\Reflection\Types\Array_
58+
* @uses \phpDocumentor\Reflection\Types\Integer
59+
* @uses \phpDocumentor\Reflection\Types\ClassString
60+
*
61+
* @covers ::__construct
62+
* @covers ::resolve
63+
*/
64+
public function testResolvingClassStrings(): void
65+
{
66+
$fixture = new TypeResolver();
67+
68+
$resolvedType = $fixture->resolve('array<int,class-string<\Foo\Bar|\Foo\Lall>>', new Context(''));
69+
70+
$this->assertInstanceOf(Array_::class, $resolvedType);
71+
$this->assertSame('array<int,class-string<\Foo\Bar|\Foo\Lall>>', (string) $resolvedType);
72+
73+
$keyType = $resolvedType->getKeyType();
74+
$valueTpye = $resolvedType->getValueType();
75+
76+
$this->assertInstanceOf(Integer::class, $keyType);
77+
78+
$this->assertInstanceOf(Compound::class, $valueTpye);
79+
foreach ($valueTpye->getIterator() as $type) {
80+
$this->assertInstanceOf(ClassString::class, $type);
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)