Skip to content

Commit

Permalink
Merge pull request #104 from sascha-egerer/issue/103
Browse files Browse the repository at this point in the history
[BUGFIX] Make QueryResultToArrayDynamicReturnTypeExtension work with CompoundType
  • Loading branch information
sascha-egerer authored Oct 26, 2022
2 parents 300a09a + c1e7147 commit 3d6d7d2
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 3 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"SaschaEgerer\\PhpstanTypo3\\Tests\\": "tests/"
},
"files": [
"tests/Unit/Type/data/repository-stub-files.php"
"tests/Unit/Type/data/repository-stub-files.php",
"tests/Unit/Type/QueryResultToArrayDynamicReturnTypeExtension/data/query-result-to-array.php"
]
},
"extra": {
Expand Down
40 changes: 38 additions & 2 deletions src/Type/QueryResultToArrayDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\ErrorType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Generic\TemplateType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use SaschaEgerer\PhpstanTypo3\Helpers\Typo3ClassNamingUtilityTrait;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;

Expand All @@ -38,12 +40,18 @@ public function getTypeFromMethodCall(
Scope $scope
): Type
{
$classReflection = $scope->getClassReflection();

$resultType = $scope->getType($methodCall->var);

if (!($resultType instanceof ObjectType)) {
$resultType = $this->getGenericTypes(
$scope->getType($methodCall->var)
)[0] ?? null;
}

if ($resultType instanceof GenericObjectType) {
$modelType = $resultType->getTypes();
} else {
$classReflection = $scope->getClassReflection();
if ($classReflection === null) {
return new ErrorType();
}
Expand All @@ -58,4 +66,32 @@ public function getTypeFromMethodCall(
return new ArrayType(new IntegerType(), $modelType[0]);
}

/**
* @return GenericObjectType[]
*/
private function getGenericTypes(Type $baseType): array
{
$genericObjectTypes = [];
TypeTraverser::map($baseType, static function (Type $type, callable $traverse) use (&$genericObjectTypes): Type {
if ($type instanceof GenericObjectType) {
$resolvedType = TypeTraverser::map($type, static function (Type $type, callable $traverse): Type {
if ($type instanceof TemplateType) {
return $traverse($type->getBound());
}
return $traverse($type);
});
if (!$resolvedType instanceof GenericObjectType) {
throw new \PHPStan\ShouldNotHappenException();
}
$genericObjectTypes[] = $resolvedType;
$traverse($type);
return $type;
}
$traverse($type);
return $type;
});

return $genericObjectTypes;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php declare(strict_types = 1);

namespace SaschaEgerer\PhpstanTypo3\Tests\Unit\Type\QueryResultToArrayDynamicReturnTypeExtension;

use PHPStan\Testing\TypeInferenceTestCase;

final class QueryResultToArrayDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
{

/**
* @return iterable<mixed>
*/
public function dataFileAsserts(): iterable
{
yield from $this->gatherAssertTypes(__DIR__ . '/data/query-result-to-array.php');
}

/**
* @dataProvider dataFileAsserts
*
* @param string $assertType
* @param string $file
* @param mixed ...$args
*/
public function testFileAsserts(
string $assertType,
string $file,
...$args
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
return [__DIR__ . '/../../../../extension.neon'];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php declare(strict_types = 1);

// phpcs:disable Squiz.Classes.ClassFileName.NoMatch
// phpcs:disable PSR1.Classes.ClassDeclaration.MultipleClasses

namespace SaschaEgerer\PhpstanTypo3\Tests\Unit\Type\QueryResultToArrayDynamicReturnTypeExtension;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Persistence\Generic\QueryResult;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;
use function PHPStan\Testing\assertType;

/**
* @extends Repository<FrontendUserGroup>
*/
class FrontendUserGroupRepository extends Repository
{

}

/**
* @extends Repository<FrontendUserGroup>
*/
class FrontendUserCustomFindAllGroupRepository extends Repository
{

/**
* @return QueryResultInterface<FrontendUserGroup>
*/
public function findAll(): QueryResultInterface // phpcs:ignore SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint
{
$queryResult = null; // phpcs:ignore SlevomatCodingStandard.Variables.UselessVariable.UselessVariable
/** @var QueryResult<FrontendUserGroup> $queryResult */
return $queryResult;
}

}

class FrontendUserGroup extends AbstractEntity
{

}

class MyController extends ActionController
{

/** @var FrontendUserGroupRepository */
private $myRepository;

/** @var FrontendUserCustomFindAllGroupRepository */
private $myCustomFindAllRepository;

public function __construct(
FrontendUserGroupRepository $myRepository,
FrontendUserCustomFindAllGroupRepository $myCustomFindAllRepository
)
{
$this->myRepository = $myRepository;
$this->myCustomFindAllRepository = $myCustomFindAllRepository;
}

public function showAction(): void
{
assertType(
'array<int, SaschaEgerer\PhpstanTypo3\Tests\Unit\Type\QueryResultToArrayDynamicReturnTypeExtension\FrontendUserGroup>',
$this->myRepository->findAll()->toArray()
);

$queryResult = $this->myRepository->findAll();
$myObjects = $queryResult->toArray();
assertType(
'array<int, SaschaEgerer\PhpstanTypo3\Tests\Unit\Type\QueryResultToArrayDynamicReturnTypeExtension\FrontendUserGroup>',
$myObjects
);

assertType(
'array<int, SaschaEgerer\PhpstanTypo3\Tests\Unit\Type\QueryResultToArrayDynamicReturnTypeExtension\FrontendUserGroup>',
$this->myCustomFindAllRepository->findAll()->toArray()
);

$queryResult = $this->myCustomFindAllRepository->findAll();
$myObjects = $queryResult->toArray();
assertType(
'array<int, SaschaEgerer\PhpstanTypo3\Tests\Unit\Type\QueryResultToArrayDynamicReturnTypeExtension\FrontendUserGroup>',
$myObjects
);
}

}
23 changes: 23 additions & 0 deletions tests/Unit/Type/data/repository-stub-files.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,26 @@ public function findAll(): array
}

}

/** @extends \TYPO3\CMS\Extbase\Persistence\Repository<\RepositoryStubFiles\My\Test\Extension\Domain\Model\MyModel> */
class FindAllWithoutReturnTestRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{

public function myTests(): void
{
assertType(
'array<int, RepositoryStubFiles\My\Test\Extension\Domain\Model\MyModel>|TYPO3\CMS\Extbase\Persistence\QueryResultInterface<RepositoryStubFiles\My\Test\Extension\Domain\Model\MyModel>',
$this->findAll()
);
}

public function findAll() // phpcs:ignore SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint
{
$foo = null; // phpcs:ignore SlevomatCodingStandard.Variables.UselessVariable.UselessVariable
/**
* @var array<int, \RepositoryStubFiles\My\Test\Extension\Domain\Model\MyModel>|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface<\RepositoryStubFiles\My\Test\Extension\Domain\Model\MyModel> $foo
*/
return $foo;
}

}

0 comments on commit 3d6d7d2

Please sign in to comment.