Passed
Push — master ( cde1e0...39b8c1 )
by Marius
02:01
created

QueryAnalyser::analyseQueryWithoutPager()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 8
cts 8
cp 1
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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 36
    public function analyseQuery(ConfiguredQuery $configuredQuery, Pager $pager)
27
    {
28 36
        $analysedQuery = $this->analyseQueryWithoutPager($configuredQuery);
29
30
        if (
31 31
            $pager->getOffset() !== null
32 31
            && $configuredQuery->hasMaximumOffset()
33 31
            && $pager->getOffset() > $configuredQuery->getMaximumOffset()
34
        ) {
35 1
            throw new TooLargeOffsetException($configuredQuery->getMaximumOffset(), $pager->getOffset());
36
        }
37
38 31
        $orderingConfigurations = $this->mapToOrderingConfigurations(
39 31
            $configuredQuery,
40 31
            $analysedQuery->getRootAlias(),
41 31
            $pager->getOrderingPairs()
42
        );
43
44
        return $analysedQuery
45 30
            ->setOrderingConfigurations($orderingConfigurations)
46
        ;
47
    }
48
49
    /**
50
     * @internal
51
     * @param ConfiguredQuery $configuredQuery
52
     * @return AnalysedQuery
53
     */
54 38
    public function analyseQueryWithoutPager(ConfiguredQuery $configuredQuery)
55
    {
56 38
        $queryBuilder = $configuredQuery->getQueryBuilder();
57 38
        $rootAlias = $this->getRootAlias($queryBuilder);
58 38
        if ($rootAlias === null) {
59 5
            throw new InvalidArgumentException('Invalid QueryBuilder passed - cannot resolve root select alias');
60
        }
61
62 33
        return (new AnalysedQuery())
63 33
            ->setQueryBuilder($queryBuilder)
64 33
            ->setRootAlias($rootAlias)
65
        ;
66
    }
67
68
    /**
69
     * @param QueryBuilder $queryBuilder
70
     * @return null|string
71
     */
72 38
    private function getRootAlias(QueryBuilder $queryBuilder)
73
    {
74 38
        $selectDqlParts = $queryBuilder->getDQLPart('select');
75 38
        if (!isset($selectDqlParts[0])) {
76 2
            return null;
77
        }
78 36
        $select = $selectDqlParts[0];
79 36
        if (!$select instanceof Select) {
80 1
            return null;
81
        }
82 35
        $selectParts = $select->getParts();
83 35
        if (!isset($selectParts[0]) || !is_string($selectParts[0])) {
84
            return null;
85
        }
86 35
        if (mb_strpos($selectParts[0], '(') !== false) {
87 1
            return null;
88
        }
89 34
        if (mb_strpos($selectParts[0], ',') !== false) {
90 1
            return null;
91
        }
92
93 33
        return $selectParts[0];
94
    }
95
96
    /**
97
     * @param ConfiguredQuery $configuredQuery
98
     * @param string $rootAlias
99
     * @param array|OrderingPair[] $orderingPairs
100
     * @return array|OrderingConfiguration[]
101
     */
102 31
    private function mapToOrderingConfigurations(ConfiguredQuery $configuredQuery, string $rootAlias, array $orderingPairs)
103
    {
104 31
        $orderingConfigurations = [];
105 31
        $idIncluded = false;
106 31
        $defaultAscending = null;
107 31
        $orderByIdExpression = sprintf('%s.%s', $rootAlias, self::ID_FIELD);
108
109 31
        foreach ($orderingPairs as $orderingPair) {
110 27
            $orderingConfiguration = $configuredQuery->getOrderingConfigurationFor($orderingPair->getOrderBy());
111
112 26
            if ($orderingPair->isOrderingDirectionSet()) {
113 25
                $orderingConfiguration->setOrderAscending($orderingPair->isOrderAscending());
114
            }
115
116 26
            if ($orderingConfiguration->getOrderByExpression() === $orderByIdExpression) {
117 26
                $idIncluded = true;
118
            }
119
120 26
            if ($defaultAscending === null) {
121 26
                $defaultAscending = $orderingConfiguration->isOrderAscending();
122
            }
123
124 26
            $orderingConfigurations[] = $orderingConfiguration;
125
        }
126
127 30
        if ($idIncluded) {
128 26
            return $orderingConfigurations;
129
        }
130
131 4
        $orderingConfigurations[] = (new OrderingConfiguration($orderByIdExpression, self::ID_FIELD))
132 4
            ->setOrderAscending($defaultAscending ?? false)
133
        ;
134
135 4
        return $orderingConfigurations;
136
    }
137
}
138