Skip to content
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

Issue with QueryBuilderGetQueryDynamicReturnTypeExtension when a variable typed as an interface is used #501

Closed
Kocal opened this issue Nov 28, 2023 · 8 comments

Comments

@Kocal
Copy link
Contributor

Kocal commented Nov 28, 2023

Hi everyone,

When upgrading from 1.3.50 to 1.3.53, we started to have the following issue:

 -- ----------------------------------------------------------------------------------------------------------------------------
     Error
 -- ----------------------------------------------------------------------------------------------------------------------------
     Internal error: Internal error: Class 'App\Common\Entity\Behavior\StatsOwnerInterface' does not exist while analysing file
     /Users/halliaume/workspace/international-website/src/Core/Handler/Analytics/StatsHandler.php
     Run PHPStan with -v option and post the stack trace to:
     https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml
     Child process error (exit code 1):
 -- ----------------------------------------------------------------------------------------------------------------------------

App\Common\Entity\Behavior\StatsOwnerInterface is indeed not a class, but it exists as an interface.

When running PHPStan with --debug:

Uncaught Doctrine\Persistence\Mapping\MappingException: Class 'App\Common\Entity\Behavior\StatsOwnerInterface' does not exist in /PATH/TO/PROJECT/vendor/doctrine/persistence/src/Persistence/Mapping/MappingException.php:80
#0 /PATH/TO/PROJECT/vendor/doctrine/persistence/src/Persistence/Mapping/RuntimeReflectionService.php(39): Doctrine\Persistence\Mapping\MappingException::nonExistingClass('App\\Common\\Enti...')
#1 /PATH/TO/PROJECT/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php(283): Doctrine\Persistence\Mapping\RuntimeReflectionService->getParentClasses('App\\Common\\Enti...')
#2 /PATH/TO/PROJECT/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php(318): Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->getParentClasses('App\\Common\\Enti...')
#3 /PATH/TO/PROJECT/vendor/doctrine/persistence/src/Persistence/Mapping/AbstractClassMetadataFactory.php(207): Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->loadMetadata('App\\Common\\Enti...')
#4 /PATH/TO/PROJECT/vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php(318): Doctrine\Persistence\Mapping\AbstractClassMetadataFactory->getMetadataFor('App\\Common\\Enti...')
#5 /PATH/TO/PROJECT/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php(1263): Doctrine\ORM\EntityManager->getClassMetadata('App\\Common\\Enti...')
#6 /PATH/TO/PROJECT/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php(898): Doctrine\ORM\Query\Parser->UpdateClause()
#7 /PATH/TO/PROJECT/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php(854): Doctrine\ORM\Query\Parser->UpdateStatement()
#8 /PATH/TO/PROJECT/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php(257): Doctrine\ORM\Query\Parser->QueryLanguage()
#9 /PATH/TO/PROJECT/vendor/doctrine/orm/lib/Doctrine/ORM/Query/Parser.php(357): Doctrine\ORM\Query\Parser->getAST()
#10 /PATH/TO/PROJECT/vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/Query/QueryResultTypeWalker.php(119): Doctrine\ORM\Query\Parser->parse()
#11 /PATH/TO/PROJECT/vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php(205): PHPStan\Type\Doctrine\Query\QueryResultTypeWalker::walk(Object(Doctrine\ORM\Query), Object(PHPStan\Type\Doctrine\Query\QueryResultTypeBuilder), Object(PHPStan\Type\Doctrine\DescriptorRegistry))
#12 /PATH/TO/PROJECT/vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/QueryBuilder/QueryBuilderGetQueryDynamicReturnTypeExtension.php(188): PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderGetQueryDynamicReturnTypeExtension->getQueryType('UPDATE App\\Comm...')
#13 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(3495): PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderGetQueryDynamicReturnTypeExtension->getTypeFromMethodCall(Object(PHPStan\Reflection\ResolvedMethodReflection), Object(PhpParser\Node\Expr\MethodCall), Object(PHPStan\Analyser\MutatingScope))
#14 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(1379): PHPStan\Analyser\MutatingScope->methodCallReturnType(Object(PHPStan\Type\Doctrine\QueryBuilder\BranchingQueryBuilderType), 'getQuery', Object(PhpParser\Node\Expr\MethodCall))
#15 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(1385): PHPStan\Analyser\MutatingScope->PHPStan\Analyser\{closure}()
#16 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(567): PHPStan\Analyser\MutatingScope->resolveType('$updateQb->getQ...', Object(PhpParser\Node\Expr\MethodCall))
#17 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(1379): PHPStan\Analyser\MutatingScope->getType(Object(PhpParser\Node\Expr\MethodCall))
#18 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(1385): PHPStan\Analyser\MutatingScope->PHPStan\Analyser\{closure}()
#19 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(567): PHPStan\Analyser\MutatingScope->resolveType('$updateQb->getQ...', Object(PhpParser\Node\Expr\MethodCall))
#20 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(1574): PHPStan\Analyser\MutatingScope->getType(Object(PhpParser\Node\Expr\MethodCall))
#21 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(627): PHPStan\Analyser\NodeScopeResolver->findEarlyTerminatingExpr(Object(PhpParser\Node\Expr\MethodCall), Object(PHPStan\Analyser\MutatingScope))
#22 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(402): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Expression), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#23 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(794): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Foreach_), Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#24 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(402): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Foreach_), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#25 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(567): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\ClassMethod), Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#26 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(3652): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\ClassMethod), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#27 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(3668): PHPStan\Analyser\NodeScopeResolver->processNodesForCalledMethod(Object(PhpParser\Node\Stmt\Class_), '/PATH/TO/PR...', Object(PHPStan\Reflection\ResolvedMethodReflection), Object(Closure))
#28 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(3664): PHPStan\Analyser\NodeScopeResolver->processNodesForCalledMethod(Array, '/PATH/TO/PR...', Object(PHPStan\Reflection\ResolvedMethodReflection), Object(Closure))
#29 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(3668): PHPStan\Analyser\NodeScopeResolver->processNodesForCalledMethod(Object(PhpParser\Node\Stmt\Namespace_), '/PATH/TO/PR...', Object(PHPStan\Reflection\ResolvedMethodReflection), Object(Closure))
#30 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(3587): PHPStan\Analyser\NodeScopeResolver->processNodesForCalledMethod(Array, '/PATH/TO/PR...', Object(PHPStan\Reflection\ResolvedMethodReflection), Object(Closure))
#31 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(1813): PHPStan\Analyser\NodeScopeResolver->processCalledMethod(Object(PHPStan\Reflection\ResolvedMethodReflection))
#32 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(628): PHPStan\Analyser\NodeScopeResolver->processExprNode(Object(PhpParser\Node\Expr\MethodCall), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\ExpressionContext))
#33 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(402): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Expression), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#34 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(567): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\ClassMethod), Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#35 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(402): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\ClassMethod), Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Node\ClassStatementsGatherer), Object(PHPStan\Analyser\StatementContext))
#36 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(669): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Class_), Array, Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Node\ClassStatementsGatherer), Object(PHPStan\Analyser\StatementContext))
#37 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(402): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Class_), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#38 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(641): PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Namespace_), Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#39 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(371): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Namespace_), Object(PHPStan\Analyser\MutatingScope), Object(Closure), Object(PHPStan\Analyser\StatementContext))
#40 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnalyser.php(166): PHPStan\Analyser\NodeScopeResolver->processNodes(Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure))
#41 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/Analyser.php(72): PHPStan\Analyser\FileAnalyser->analyseFile('/PATH/TO/PR...', Array, Object(PHPStan\Rules\LazyRegistry), Object(PHPStan\Collectors\Registry), NULL)
#42 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Command/AnalyserRunner.php(62): PHPStan\Analyser\Analyser->analyse(Array, Object(Closure), NULL, true, Array)
#43 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Command/AnalyseApplication.php(209): PHPStan\Command\AnalyserRunner->runAnalyser(Array, Array, Object(Closure), NULL, true, true, '/PATH/TO/PR...', Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Input\ArgvInput))
#44 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Command/AnalyseApplication.php(101): PHPStan\Command\AnalyseApplication->runAnalyser(Array, Array, true, '/PATH/TO/PR...', Object(PHPStan\Command\Symfony\SymfonyOutput), Object(PHPStan\Command\Symfony\SymfonyOutput), Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Input\ArgvInput))
#45 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Command/AnalyseCommand.php(198): PHPStan\Command\AnalyseApplication->analyse(Array, false, Object(PHPStan\Command\Symfony\SymfonyOutput), Object(PHPStan\Command\Symfony\SymfonyOutput), false, true, '/PATH/TO/PR...', Array, Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Input\ArgvInput))
#46 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Command/Command.php(259): PHPStan\Command\AnalyseCommand->execute(Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Output\ConsoleOutput))
#47 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(870): _PHPStan_d147f4cc6\Symfony\Component\Console\Command\Command->run(Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Output\ConsoleOutput))
#48 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(261): _PHPStan_d147f4cc6\Symfony\Component\Console\Application->doRunCommand(Object(PHPStan\Command\AnalyseCommand), Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Output\ConsoleOutput))
#49 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(157): _PHPStan_d147f4cc6\Symfony\Component\Console\Application->doRun(Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_d147f4cc6\Symfony\Component\Console\Output\ConsoleOutput))
#50 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(124): _PHPStan_d147f4cc6\Symfony\Component\Console\Application->run()
#51 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(125): _PHPStan_d147f4cc6\{closure}()
#52 /PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan(8): require('phar:https:///Users/h...')
#53 /PATH/TO/PROJECT/vendor/bin/phpstan(119): include('/PATH/TO/PR...')
#54 {main}

We can see the exception is thrown at this line:

#16 phar:https:///PATH/TO/PROJECT/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(567): PHPStan\Analyser\MutatingScope->resolveType('$updateQb->getQ...', Object(PhpParser\Node\Expr\MethodCall))

which refers to the following PHP code:

<?php
class A {
	/**
	 * @param array<string, StatsOwnerInterface> $entitiesByUrl
	 */
	private function retrieveStatsForCurrentSlidingPeriod(
	    Website $website,
	    HasStats $statSettings,
	    \DateTimeZone $timezone,
	    array $entitiesByUrl,
	): void {
	    $periodStart = $this->getPeriodStart($statSettings, $timezone);
	    $periodEnd = $this->getPeriodEnd($timezone);
	    $lastPeriodStats = $this->googleReportingApi->getStatsForPeriod(
	        $website,
	        $periodStart,
	        $periodEnd,
	        array_keys($entitiesByUrl),
	        $this->getEntitiesIds($entitiesByUrl),
	        $statSettings,
	    );
	    foreach ($lastPeriodStats as $url => $reportData) {
	        if (null === $ownerEntity = ($entitiesByUrl[$url] ?? null)) {
	            continue;
	        }
	        $updateQb = $this->entityManager
	            ->getRepository($ownerEntity::class)
	            ->createQueryBuilder('entity');
	        $updateQb->update()
	            ->set('entity.lastPageViewCount', $reportData->pageviews)
	            ->set('entity.lastSessionCount', $reportData->sessions)
	            ->where('entity = :entity')
	            ->setParameter('entity', $ownerEntity);
	        $updateQb->getQuery()->execute();
	    }
	    $this->entityManager->flush();
	}
}

Here, the $ownerEntity (used in $this->entityManager->getRepository()) is an implementation of StatsOwnerInterface, due to the method's PHPDoc, and so it makes the extension crash.

I didn't check the extension code yet, but maybe early-checking if we really have a class would be great, or catch a MappingException exception to return a new QueryType($dql, null).

WDYT?

@ondrejmirtes
Copy link
Member

Hi, did you configure objectManagerLoader or not? See the README.

@Kocal
Copy link
Contributor Author

Kocal commented Nov 28, 2023

Yes the objectManagerLoader is configured with this file (since the 23rd october 2021, and it worked fine until yet):

<?php
// phpstan/object-manager.php
/**
 * This file is used by PHPStan, see https://github.com/phpstan/phpstan-doctrine#configuration.
 */

declare(strict_types=1);

use App\Kernel;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Dotenv\Dotenv;

(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();

/** @var EntityManager $entityManager */
$entityManager = $kernel->getContainer()->get('doctrine')->getManager();

return $entityManager;

But since $ownerEntity is seen as an interface, PHPStan/Doctrine could not infer types

@ondrejmirtes
Copy link
Member

PHPStan just uses Doctrine internals to infer the query type. How come that when called from PHPStan the internals crash but within your application the code is okay?

@Kocal
Copy link
Contributor Author

Kocal commented Nov 28, 2023

Because at runtime, $ownerEntity is an implementation of App\Common\Entity\Behavior\StatsOwnerInterface and so everything is fine, the entity is nicely persisted.

But when running PHPStan on it, $ownerEntity type could not be inferred and it stays App\Common\Entity\Behavior\StatsOwnerInterface (because of how $entitiesByUrl is typed).

Kocal added a commit to Kocal/phpstan-doctrine that referenced this issue Dec 14, 2023
@Xen3r0
Copy link

Xen3r0 commented Dec 29, 2023

I have a same problem.

Did you have any solution ?

@Kocal
Copy link
Contributor Author

Kocal commented Dec 29, 2023

I didn't look any further than that, I've explicitly pinned the version 1.3.50 in order to deal with those issues later :/

@janedbal
Copy link
Contributor

This should be fixed since 1.3.55

Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants