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

order of QueryBuilder::select arguments is not preserved in QueryBuilder::getResult #482

Open
CircleCode opened this issue Nov 3, 2023 · 1 comment

Comments

@CircleCode
Copy link

CircleCode commented Nov 3, 2023

According to

default:
if ($queryKeyType->isNull()->yes()) {
return AccessoryArrayListType::intersectWith(new ArrayType(
new IntegerType(),
$queryResultType
));
}
return new ArrayType(
$queryKeyType,
$queryResultType
);
, QueryBuilder::getResult returns an array withh all returned types, and does not preserve order.

As an example, we had this code:

    /*
     * @return array{ 0: SearchCustomization | null, 1: LocalizationDetail | null }
     */
    public function findLandingInformation(string $url, string $locale): array
    {
        $queryBuilder = $this->getEntityManager()->createQueryBuilder()
            ->select('sc, ld')
            ->from(SearchCustomization::class, 'sc')
            ->innerJoin(SearchCustomizationText::class, 'sct', Join::WITH, 'sct.searchCustomization = sc')
            ->innerJoin(Language::class, 'l', Join::WITH, 'sct.language = l')
            ->leftJoin(LocalizationDetail::class, 'ld', Join::WITH, 'ld.localization = sc.localization')
                    ->where('sct.url = :url')
                    ->andWhere('l.code = :language')
                    ->andWhere('sc.isActive = 1')
                    ->andWhere('ld.language = l')
                    ->setParameter('url', $url)
                    ->setParameter('language', $locale);

        return $queryBuilder->getQuery()->getResult();
    }

and phpstan complained that

Method findLandingInformation()  
         should return array{Api\Entity\SearchCustomization|null,                        
         Api\Entity\LocalizationDetail|null} but returns array<int,                      
         Api\Entity\LocalizationDetail|Api\Entity\SearchCustomization|null>.

so we had to write this to help phpstan understand:

    /*
     * @return array{ 0: SearchCustomization | null, 1: LocalizationDetail | null }
     */
    public function findLandingInformation(string $url, string $locale): array
    {
        $queryBuilder = $this->getEntityManager()->createQueryBuilder()
            ->select('sc, ld')
            ->from(SearchCustomization::class, 'sc')
            ->innerJoin(SearchCustomizationText::class, 'sct', Join::WITH, 'sct.searchCustomization = sc')
            ->innerJoin(Language::class, 'l', Join::WITH, 'sct.language = l')
            ->leftJoin(LocalizationDetail::class, 'ld', Join::WITH, 'ld.localization = sc.localization')
                    ->where('sct.url = :url')
                    ->andWhere('l.code = :language')
                    ->andWhere('sc.isActive = 1')
                    ->andWhere('ld.language = l')
                    ->setParameter('url', $url)
                    ->setParameter('language', $locale);

        [$searchCustomization, $localizationDetail] = $queryBuilder->getQuery()->getResult();

        return [
            is_a($searchCustomization, SearchCustomization::class) ? $searchCustomization : null,
            is_a($localizationDetail, LocalizationDetail::class) ? $localizationDetail : null,
        ];
    }
@CircleCode CircleCode changed the title order of select order of QueryBuilder::select arguments is not preserved in QueryBuilder::getResults Nov 3, 2023
@CircleCode CircleCode changed the title order of QueryBuilder::select arguments is not preserved in QueryBuilder::getResults order of QueryBuilder::select arguments is not preserved in QueryBuilder::getResult Nov 3, 2023
@VincentLanglet
Copy link
Contributor

Hi @CircleCode, it better start with how the Query is templated
https://github.com/phpstan/phpstan-doctrine/blob/1.4.x/src/Type/Doctrine/Query/QueryType.php
similar to an array array<Tkey, Tresult>
https://github.com/phpstan/phpstan-src/blob/1.11.x/src/Type/ArrayType.php

You can try

$query = $this->getEntityManager()->createQueryBuilder()
            ->select('sc, ld')
            ->from(SearchCustomization::class, 'sc')
            ->innerJoin(SearchCustomizationText::class, 'sct', Join::WITH, 'sct.searchCustomization = sc')
            ->innerJoin(Language::class, 'l', Join::WITH, 'sct.language = l')
            ->leftJoin(LocalizationDetail::class, 'ld', Join::WITH, 'ld.localization = sc.localization')
            ->getQuery();
\PHPstan\dumpType($query);

You'll get something like `Query<array-key, SearchCustomization | LocalizationDetail>

To support keeping shape, I think we need a Query shape like we have array shape
https://github.com/phpstan/phpstan-src/blob/1.11.x/src/Type/Constant/ConstantArrayType.php
and we need to change both the

new QueryType($queryString, $typeBuilder->getIndexType(), $typeBuilder->getResultType());

calls and the simplification made by the typeBuilder here
https://github.com/phpstan/phpstan-doctrine/blob/1.4.x/src/Type/Doctrine/Query/QueryResultTypeBuilder.php#L214

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants