Skip to content

fix for multiple class-string classes #157

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 47 additions & 7 deletions src/TypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use InvalidArgumentException;
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
use phpDocumentor\Reflection\PseudoTypes\List_;
use phpDocumentor\Reflection\Types\AggregatedType;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\ArrayKey;
use phpDocumentor\Reflection\Types\ClassString;
Expand All @@ -36,6 +37,7 @@
use function array_key_exists;
use function array_pop;
use function array_values;
use function assert;
use function class_exists;
use function class_implements;
use function count;
Expand Down Expand Up @@ -261,7 +263,9 @@ private function parseTypes(ArrayIterator $tokens, Context $context, int $parser
$classType = array_pop($types);
if ($classType !== null) {
if ((string) $classType === 'class-string') {
$types[] = $this->resolveClassString($tokens, $context);
foreach ($this->resolveClassString($tokens, $context) as $classStringType) {
$types[] = $classStringType;
}
} elseif ((string) $classType === 'int') {
$types[] = $this->resolveIntRange($tokens);
} elseif ((string) $classType === 'interface-string') {
Expand Down Expand Up @@ -461,17 +465,41 @@ private function resolveTypedObject(string $type, ?Context $context = null): Obj
* Resolves class string
*
* @param ArrayIterator<int, (string|null)> $tokens
*
* @return array<int, ClassString>
*/
private function resolveClassString(ArrayIterator $tokens, Context $context): Type
private function resolveClassString(ArrayIterator $tokens, Context $context): array
{
$tokens->next();

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

if (!$classType instanceof Object_ || $classType->getFqsen() === null) {
throw new RuntimeException(
$classType . ' is not a class string'
);
$aggreatedObjects = false;
if ($classType instanceof AggregatedType) {
foreach ($classType->getIterator() as $typeInner) {
if (!$typeInner instanceof Object_) {
break;
}
}

$aggreatedObjects = true;
}

if ($aggreatedObjects) {
assert($classType instanceof AggregatedType);
foreach ($classType->getIterator() as $typeInner) {
if (!$typeInner instanceof Object_ || $typeInner->getFqsen() === null) {
throw new RuntimeException(
$typeInner . ' is not a class string'
);
}
}
} else {
if (!$classType instanceof Object_ || $classType->getFqsen() === null) {
throw new RuntimeException(
$classType . ' is not a class string'
);
}
}

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

return new ClassString($classType->getFqsen());
$return = [];
if ($aggreatedObjects) {
assert($classType instanceof AggregatedType);
foreach ($classType->getIterator() as $typeInner) {
assert($typeInner instanceof Object_);
$return[] = new ClassString($typeInner->getFqsen());
}
} else {
assert($classType instanceof Object_);
$return[] = new ClassString($classType->getFqsen());
}

return $return;
}

/**
Expand Down
27 changes: 27 additions & 0 deletions src/Types/AbstractList.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

use phpDocumentor\Reflection\Type;

use function count;
use function implode;

/**
* Represents a list of values. This is an abstract class for Array_ and Collection.
*
Expand Down Expand Up @@ -67,6 +70,30 @@ public function getValueType(): Type
public function __toString(): string
{
if ($this->keyType) {
$classStringValues = [];
if ($this->valueType instanceof AggregatedType) {
/**
* @psalm-suppress ImpureMethodCall
*/
foreach ($this->valueType->getIterator() as $typeInner) {
if (!($typeInner instanceof ClassString)) {
$classStringValues = [];
break;
}

$fqsenTmp = $typeInner->getFqsen();
if (!$fqsenTmp) {
continue;
}

$classStringValues[] = $fqsenTmp->__toString();
}
}

if (count($classStringValues) > 0) {
return 'array<' . $this->keyType . ',class-string<' . implode('|', $classStringValues) . '>>';
}

return 'array<' . $this->keyType . ',' . $this->valueType . '>';
}

Expand Down
1 change: 0 additions & 1 deletion src/Types/AggregatedType.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ public function has(int $index): bool
public function contains(Type $type): bool
{
foreach ($this->types as $typePart) {
// if the type is duplicate; do not add it
if ((string) $typePart === (string) $type) {
return true;
}
Expand Down
83 changes: 83 additions & 0 deletions tests/unit/ClassStringResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

declare(strict_types=1);

/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/

namespace phpDocumentor\Reflection;

use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\ClassString;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Integer;
use PHPUnit\Framework\TestCase;

/**
* @covers ::<private>
* @coversDefaultClass \phpDocumentor\Reflection\TypeResolver
*/
class ClassStringResolverTest extends TestCase
{
/**
* @uses \phpDocumentor\Reflection\Types\Array_
* @uses \phpDocumentor\Reflection\Types\Integer
* @uses \phpDocumentor\Reflection\Types\ClassString
*
* @covers ::__construct
* @covers ::resolve
*/
public function testResolvingClassString(): void
{
$fixture = new TypeResolver();

$resolvedType = $fixture->resolve('array<int,class-string<\Foo\Bar>>', new Context(''));

$this->assertInstanceOf(Array_::class, $resolvedType);
$this->assertSame('array<int,class-string<\Foo\Bar>>', (string) $resolvedType);

$keyType = $resolvedType->getKeyType();
$valueTpye = $resolvedType->getValueType();

$this->assertInstanceOf(Integer::class, $keyType);

$this->assertInstanceOf(ClassString::class, $valueTpye);
$this->assertSame('Bar', $valueTpye->getFqsen()->getName());
$this->assertSame('\Foo\Bar', $valueTpye->getFqsen()->__toString());
}

/**
* @uses \phpDocumentor\Reflection\Types\Array_
* @uses \phpDocumentor\Reflection\Types\Integer
* @uses \phpDocumentor\Reflection\Types\ClassString
*
* @covers ::__construct
* @covers ::resolve
*/
public function testResolvingClassStrings(): void
{
$fixture = new TypeResolver();

$resolvedType = $fixture->resolve('array<int,class-string<\Foo\Bar|\Foo\Lall>>', new Context(''));

$this->assertInstanceOf(Array_::class, $resolvedType);
$this->assertSame('array<int,class-string<\Foo\Bar|\Foo\Lall>>', (string) $resolvedType);

$keyType = $resolvedType->getKeyType();
$valueTpye = $resolvedType->getValueType();

$this->assertInstanceOf(Integer::class, $keyType);

$this->assertInstanceOf(Compound::class, $valueTpye);
foreach ($valueTpye->getIterator() as $type) {
$this->assertInstanceOf(ClassString::class, $type);
}
}
}