Completed
Push — master ( 8053c5...9d3813 )
by Kévin
10s
created

FilterEagerLoadingExtension::applyToCollection()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 15
nc 5
nop 4
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
namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Extension;
13
14
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
15
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
16
use Doctrine\ORM\Query\Expr\Join;
17
use Doctrine\ORM\QueryBuilder;
18
19
/**
20
 * Fixes filters on OneToMany associations
21
 * https://github.com/api-platform/core/issues/944.
22
 */
23
final class FilterEagerLoadingExtension implements QueryCollectionExtensionInterface
24
{
25
    private $resourceMetadataFactory;
26
    private $forceEager;
27
28
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, $forceEager = true)
29
    {
30
        $this->resourceMetadataFactory = $resourceMetadataFactory;
31
        $this->forceEager = $forceEager;
32
    }
33
34
    /**
35
     * {@inheritdoc}
36
     */
37
    public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
38
    {
39
        if (false === $this->forceEager || false === $this->isForceEager($resourceClass, ['collection_operation_name' => $operationName])) {
40
            return;
41
        }
42
43
        //If no where part, nothing to do
44
        $wherePart = $queryBuilder->getDQLPart('where');
45
46
        if (!$wherePart) {
47
            return;
48
        }
49
50
        $joinParts = $queryBuilder->getDQLPart('join');
51
52
        if (!$joinParts || !isset($joinParts['o'])) {
53
            return;
54
        }
55
56
        $queryBuilderClone = clone $queryBuilder;
57
        $queryBuilderClone->resetDQLPart('where');
58
        $queryBuilderClone->andWhere($queryBuilderClone->expr()->in('o', $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator)->getDQL()));
59
60
        $queryBuilder->resetDQLPart('where');
61
        foreach ($queryBuilderClone->getDQLPart('where')->getParts() as $wherePart) {
62
            $queryBuilder->add('where', $wherePart);
63
        }
64
    }
65
66
    /**
67
     * Returns a clone of the given query builder where everything gets re-aliased.
68
     *
69
     * @param QueryBuilder                $queryBuilder
70
     * @param QueryNameGeneratorInterface $queryBuilder
71
     * @param string                      $originAlias  - the base alias
72
     * @param string                      $replacement  - the replacement for the base alias, will change the from alias
73
     */
74
    private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $originAlias = 'o', string $replacement = 'o_2')
75
    {
76
        $queryBuilderClone = clone $queryBuilder;
77
        $queryBuilderClone->select($replacement);
78
79
        $joinParts = $queryBuilder->getDQLPart('join');
80
        $wherePart = $queryBuilder->getDQLPart('where');
81
82
        //reset parts
83
        $queryBuilderClone->resetDQLPart('join');
84
        $queryBuilderClone->resetDQLPart('where');
85
86
        //Change from alias
87
        $from = $queryBuilderClone->getDQLPart('from')[0];
88
        $queryBuilderClone->resetDQLPart('from');
89
        $queryBuilderClone->from($from->getFrom(), $replacement);
90
91
        $aliases = ["$originAlias."];
92
        $replacements = ["$replacement."];
93
94
        //Change join aliases
95
        foreach ($joinParts[$originAlias] as $joinPart) {
96
            $aliases[] = "{$joinPart->getAlias()}.";
97
            $alias = $queryNameGenerator->generateJoinAlias($joinPart->getAlias());
98
            $replacements[] = "$alias.";
99
            $join = new Join($joinPart->getJoinType(), str_replace($aliases, $replacements, $joinPart->getJoin()), $alias, $joinPart->getConditionType(), $joinPart->getCondition(), $joinPart->getIndexBy());
100
101
            $queryBuilderClone->add('join', [$join], true);
0 ignored issues
show
Documentation introduced by
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...
102
        }
103
104
        //Change where aliases
105
        foreach ($wherePart->getParts() as $where) {
106
            $queryBuilderClone->add('where', str_replace($aliases, $replacements, $where));
107
        }
108
109
        return $queryBuilderClone;
110
    }
111
112
    /**
113
     * Does an operation force eager?
114
     *
115
     * @param string $resourceClass
116
     * @param array  $options
117
     *
118
     * @return bool
119
     */
120
    private function isForceEager(string $resourceClass, array $options): bool
121
    {
122
        $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
123
124
        if (isset($options['collection_operation_name'])) {
125
            $forceEager = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], 'force_eager', null, true);
126
        } else {
127
            $forceEager = $resourceMetadata->getAttribute('force_eager');
128
        }
129
130
        return is_bool($forceEager) ? $forceEager : $this->forceEager;
131
    }
132
}
133