Completed
Pull Request — master (#9)
by
unknown
05:08 queued 02:27
created

QueryAnalyser   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 128
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 96.36%

Importance

Changes 0
Metric Value
wmc 20
lcom 1
cbo 7
dl 0
loc 128
ccs 53
cts 55
cp 0.9636
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A analyseQuery() 0 25 5
A analyseQueryWithoutPager() 0 13 2
B getRootAlias() 0 23 7
B mapToOrderingConfigurations() 0 38 6
1
<?php
2
declare(strict_types=1);
3
4
namespace Paysera\Pagination\Service\Doctrine;
5
6
use Doctrine\ORM\Query\Expr\Select;
7
use Doctrine\ORM\QueryBuilder;
8
use Paysera\Pagination\Entity\Doctrine\AnalysedQuery;
9
use Paysera\Pagination\Entity\OrderingConfiguration;
10
use Paysera\Pagination\Entity\Doctrine\ConfiguredQuery;
11
use Paysera\Pagination\Entity\OrderingPair;
12
use Paysera\Pagination\Entity\Pager;
13
use InvalidArgumentException;
14
use Paysera\Pagination\Exception\TooLargeOffsetException;
15
16
class QueryAnalyser
17
{
18
    const ID_FIELD = 'id';
19
20
    /**
21
     * @internal
22
     * @param ConfiguredQuery $configuredQuery
23
     * @param Pager $pager
24
     * @return AnalysedQuery
25
     */
26 41
    public function analyseQuery(ConfiguredQuery $configuredQuery, Pager $pager): AnalysedQuery
27
    {
28 41
        $analysedQuery = $this->analyseQueryWithoutPager($configuredQuery);
29 36
        if ($configuredQuery->getQueryModifier() !== null) {
30
            $analysedQuery->setQueryModifier($configuredQuery->getQueryModifier());
31
        }
32
33
        if (
34 36
            $pager->getOffset() !== null
35 36
            && $configuredQuery->hasMaximumOffset()
36 36
            && $pager->getOffset() > $configuredQuery->getMaximumOffset()
37
        ) {
38 1
            throw new TooLargeOffsetException($configuredQuery->getMaximumOffset(), $pager->getOffset());
39
        }
40
41 36
        $orderingConfigurations = $this->mapToOrderingConfigurations(
42 36
            $configuredQuery,
43 36
            $analysedQuery->getRootAlias(),
44 36
            $pager->getOrderingPairs()
45
        );
46
47
        return $analysedQuery
48 35
            ->setOrderingConfigurations($orderingConfigurations)
49
        ;
50
    }
51
52
    /**
53
     * @internal
54
     * @param ConfiguredQuery $configuredQuery
55
     * @return AnalysedQuery
56
     */
57 47
    public function analyseQueryWithoutPager(ConfiguredQuery $configuredQuery): AnalysedQuery
58
    {
59 47
        $queryBuilder = $configuredQuery->getQueryBuilder();
60 47
        $rootAlias = $this->getRootAlias($queryBuilder);
61 47
        if ($rootAlias === null) {
62 5
            throw new InvalidArgumentException('Invalid QueryBuilder passed - cannot resolve root select alias');
63
        }
64
65 42
        return (new AnalysedQuery())
66 42
            ->setQueryBuilder($queryBuilder)
67 42
            ->setRootAlias($rootAlias)
68
        ;
69
    }
70
71
    /**
72
     * @param QueryBuilder $queryBuilder
73
     * @return null|string
74
     */
75 47
    private function getRootAlias(QueryBuilder $queryBuilder)
76
    {
77 47
        $selectDqlParts = $queryBuilder->getDQLPart('select');
78 47
        if (!isset($selectDqlParts[0])) {
79 2
            return null;
80
        }
81 45
        $select = $selectDqlParts[0];
82 45
        if (!$select instanceof Select) {
83 1
            return null;
84
        }
85 44
        $selectParts = $select->getParts();
86 44
        if (!isset($selectParts[0]) || !is_string($selectParts[0])) {
87
            return null;
88
        }
89 44
        if (mb_strpos($selectParts[0], '(') !== false) {
90 1
            return null;
91
        }
92 43
        if (mb_strpos($selectParts[0], ',') !== false) {
93 1
            return null;
94
        }
95
96 42
        return $selectParts[0];
97
    }
98
99
    /**
100
     * @param ConfiguredQuery $configuredQuery
101
     * @param string $rootAlias
102
     * @param array|OrderingPair[] $orderingPairs
103
     * @return array|OrderingConfiguration[]
104
     */
105 36
    private function mapToOrderingConfigurations(
106
        ConfiguredQuery $configuredQuery,
107
        string $rootAlias,
108
        array $orderingPairs
109
    ): array {
110 36
        $orderingConfigurations = [];
111 36
        $idIncluded = false;
112 36
        $defaultAscending = null;
113 36
        $orderByIdExpression = sprintf('%s.%s', $rootAlias, self::ID_FIELD);
114
115 36
        foreach ($orderingPairs as $orderingPair) {
116 31
            $orderingConfiguration = $configuredQuery->getOrderingConfigurationFor($orderingPair->getOrderBy());
117
118 30
            if ($orderingPair->isOrderingDirectionSet()) {
119 29
                $orderingConfiguration->setOrderAscending($orderingPair->isOrderAscending());
120
            }
121
122 30
            if ($orderingConfiguration->getOrderByExpression() === $orderByIdExpression) {
123 26
                $idIncluded = true;
124
            }
125
126 30
            if ($defaultAscending === null) {
127 30
                $defaultAscending = $orderingConfiguration->isOrderAscending();
128
            }
129
130 30
            $orderingConfigurations[] = $orderingConfiguration;
131
        }
132
133 35
        if ($idIncluded) {
134 26
            return $orderingConfigurations;
135
        }
136
137 9
        $orderingConfigurations[] = (new OrderingConfiguration($orderByIdExpression, self::ID_FIELD))
138 9
            ->setOrderAscending($defaultAscending ?? false)
139
        ;
140
141 9
        return $orderingConfigurations;
142
    }
143
}
144