phpstan-doctrine icon indicating copy to clipboard operation
phpstan-doctrine copied to clipboard

Return value of <MyClass extends EntityRepository>::createQueryBuilder does not resolve to correct type when phpstan-doctrine is used

Open themasch opened this issue 2 years ago • 2 comments

So we have this case where we override the createQueryBuilder method in one of our entity repositories to provide a specialised QueryBuilder that has some helper methods to work with the entity in question.

Heres a small test case that represents what we do in way less code:

<?php /** namespaces and uses-statements ... */ 

/** @extends ServiceEntityRepository<TestEntity> */
class TestEntityRepository extends ServiceEntityRepository
{
    /** constructor... */ 

    public function createQueryBuilder($alias, $indexBy = null): TestQueryBuilder
    {
        return new TestQueryBuilder($this->_em);
    }

    public function queryAllWithMagic(): array
    {
        $qb = $this->createQueryBuilder('tst');
        \PHPStan\dumpType($qb);
        return $qb->addMySpecialMagic()->getQuery()->getResult();
    }
}

TestQueryBuilder is just simply class TestQueryBuilder extends QueryBuilder with a few methods like for example addMySpecialMagic(): self.

When the phpstan/phpstan-doctrine extension is not installed, it dumps the type I would expect it to:

 ------ ------------------------------------------------ 
  Line   Repository\TestEntityRepository.php             
 ------ ------------------------------------------------
  26     Dumped type: App\QueryBuilder\TestQueryBuilder
 ------ ------------------------------------------------

But when it is installed, we get this instead:

 ------ ----------------------------------------
  Line   Repository\TestEntityRepository.php
 ------ ----------------------------------------
  33     Dumped type: Doctrine\ORM\QueryBuilder
 ------ ----------------------------------------

Interesting enough, it does not complain about addMySpecialMagic being an undefined method, which is what triggered the whole investation for us.

These are the versions used for this test:

  • phpstan/phpstan 1.9.12
  • phpstan/phpstan-doctrine 1.3.32 ( we also observed the same behaviour with v1.3.27)
  • doctrine/orm 2.14.1

Test project is based on symfony/[email protected], not sure if that should make a difference.

I can share the sample project I created to reproduce this if you want me. I also tried to recreate it as a testcase in the tests for this project, but failed. Could be related to the repository being ServiceEntityRepository instead of a classic EntityRepository, or maybe some other thing related to installed libraries, or maybe I am just not smart enough.

As a work around we add a /** @var TestQueryBuilder $qb */ type annotation to the code. It works, but the IDE complaints that its redudant and I would feel better if we knew why its required.

Oh, I hope this is not a duplicate. I tried to find if it has been already reported, but had no luck.

themasch avatar Jan 17 '23 16:01 themasch

There's this extension: https://github.com/phpstan/phpstan-doctrine/blob/1.3.x/src/Type/Doctrine/QueryBuilder/EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension.php

What it does is that it translates any EntityRepository::createQueryBuilder() method call into:

$entityRepository->getEntityManager()->createQueryBuilder()->select(TestEntity::class)->from().

That's why you're seeing what you're seeing.

Here's the PR that did that: https://github.com/phpstan/phpstan-doctrine/pull/140

And here's why: https://github.com/phpstan/phpstan-doctrine/issues/66

Arguably it could be refactored to support your use-case too.

ondrejmirtes avatar Jan 17 '23 16:01 ondrejmirtes

Hey, thanks for the fast response. Since we seem to be the only one having this problem for now I guess its only fair that I'll try to solve it without causing some regresssion.

Thanks for the detailed information, that saves a lot of time!

themasch avatar Jan 18 '23 09:01 themasch