Completed
Push — master ( 29b346...1faa7f )
by Amrouche
19s
created

Orm/Extension/FilterEagerLoadingExtension.php (2 issues)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Extension;
15
16
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\EagerLoadingTrait;
17
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
18
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
19
use Doctrine\ORM\Query\Expr\Join;
20
use Doctrine\ORM\QueryBuilder;
21
22
/**
23
 * Fixes filters on OneToMany associations
24
 * https://github.com/api-platform/core/issues/944.
25
 */
26
final class FilterEagerLoadingExtension implements QueryCollectionExtensionInterface
27
{
28
    use EagerLoadingTrait;
29
30
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, bool $forceEager = true)
31
    {
32
        $this->resourceMetadataFactory = $resourceMetadataFactory;
33
        $this->forceEager = $forceEager;
34
    }
35
36
    /**
37
     * {@inheritdoc}
38
     */
39
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
40
    {
41
        $em = $queryBuilder->getEntityManager();
42
        $classMetadata = $em->getClassMetadata($resourceClass);
43
44
        if (!$this->shouldOperationForceEager($resourceClass, ['collection_operation_name' => $operationName]) && !$this->hasFetchEagerAssociation($em, $classMetadata)) {
45
            return;
46
        }
47
48
        //If no where part, nothing to do
49
        $wherePart = $queryBuilder->getDQLPart('where');
50
51
        if (!$wherePart) {
52
            return;
53
        }
54
55
        $joinParts = $queryBuilder->getDQLPart('join');
56
        $originAlias = 'o';
57
58
        if (!$joinParts || !isset($joinParts[$originAlias])) {
59
            return;
60
        }
61
62
        $queryBuilderClone = clone $queryBuilder;
63
        $queryBuilderClone->resetDQLPart('where');
64
65
        if (!$classMetadata->isIdentifierComposite) {
66
            $replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias);
67
            $in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias);
68
            $in->select($replacementAlias);
69
            $queryBuilderClone->andWhere($queryBuilderClone->expr()->in($originAlias, $in->getDQL()));
70
        } else {
71
            // Because Doctrine doesn't support WHERE ( foo, bar ) IN () (https://github.com/doctrine/doctrine2/issues/5238), we are building as many subqueries as they are identifiers
72
            foreach ($classMetadata->identifier as $identifier) {
73
                $replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias);
74
                $in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias);
75
                $in->select("IDENTITY($replacementAlias.$identifier)");
76
                $queryBuilderClone->andWhere($queryBuilderClone->expr()->in("$originAlias.$identifier", $in->getDQL()));
77
            }
78
        }
79
80
        $queryBuilder->resetDQLPart('where');
81
        $queryBuilder->add('where', $queryBuilderClone->getDQLPart('where'));
82
    }
83
84
    /**
85
     * Returns a clone of the given query builder where everything gets re-aliased.
86
     *
87
     * @param QueryBuilder                $queryBuilder
88
     * @param QueryNameGeneratorInterface $queryNameGenerator
89
     * @param string                      $originAlias        the base alias
90
     * @param string                      $replacement        the replacement for the base alias, will change the from alias
91
     *
92
     * @return QueryBuilder
93
     */
94
    private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $originAlias = 'o', string $replacement = 'o_2')
95
    {
96
        $queryBuilderClone = clone $queryBuilder;
97
98
        $joinParts = $queryBuilder->getDQLPart('join');
99
        $wherePart = $queryBuilder->getDQLPart('where');
100
101
        //reset parts
102
        $queryBuilderClone->resetDQLPart('join');
103
        $queryBuilderClone->resetDQLPart('where');
104
        $queryBuilderClone->resetDQLPart('orderBy');
105
        $queryBuilderClone->resetDQLPart('groupBy');
106
        $queryBuilderClone->resetDQLPart('having');
107
108
        //Change from alias
109
        $from = $queryBuilderClone->getDQLPart('from')[0];
110
        $queryBuilderClone->resetDQLPart('from');
111
        $queryBuilderClone->from($from->getFrom(), $replacement);
112
113
        $aliases = ["$originAlias."];
114
        $replacements = ["$replacement."];
115
116
        //Change join aliases
117
        foreach ($joinParts[$originAlias] as $joinPart) {
118
            $aliases[] = "{$joinPart->getAlias()}.";
119
            $alias = $queryNameGenerator->generateJoinAlias($joinPart->getAlias());
120
            $replacements[] = "$alias.";
121
            $join = new Join($joinPart->getJoinType(), str_replace($aliases, $replacements, $joinPart->getJoin()), $alias, $joinPart->getConditionType(), str_replace($aliases, $replacements, $joinPart->getCondition()), $joinPart->getIndexBy());
122
123
            $queryBuilderClone->add('join', [$join], true);
0 ignored issues
show
array($join) is of type array<integer,object<Doc...\\Query\\Expr\\Join>"}>, but the function expects a object<Doctrine\ORM\Query\Expr\Base>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
124
        }
125
126
        $queryBuilderClone->add('where', str_replace($aliases, $replacements, (string) $wherePart));
0 ignored issues
show
str_replace($aliases, $r...s, (string) $wherePart) is of type string, but the function expects a object<Doctrine\ORM\Query\Expr\Base>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
127
128
        return $queryBuilderClone;
129
    }
130
}
131