Passed
Pull Request — master (#10)
by Gocha
11:52
created

ResultProvider   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 328
Duplicated Lines 60.67 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 54
lcom 1
cbo 11
dl 199
loc 328
ccs 0
cts 240
cp 0
rs 6.4799
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getResultForQuery() 20 20 4
A buildResult() 20 20 2
A findItems() 12 12 2
B pageQueryBuilder() 25 25 6
A applyOrdering() 0 9 3
A applyOffset() 0 4 1
A applyAfter() 0 4 1
A applyBefore() 0 4 1
B applyCursor() 0 42 10
B buildResultForEmptyItems() 33 33 6
A buildResultForZeroLimit() 20 20 2
A buildResultForTooLargeOffset() 20 20 2
A reverseOrderingDirection() 10 10 2
A existsBeforeCursor() 9 9 1
A existsAfterCursor() 9 9 1
A calculateTotalCount() 12 12 6
A findCount() 0 6 1
A transformResultItems() 9 9 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ResultProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ResultProvider, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
4
namespace Paysera\Pagination\Service\ODM;
5
6
use Doctrine\ODM\MongoDB\Query\Builder as QueryBuilder;
7
use Paysera\Pagination\Entity\ODM\AnalysedQuery;
8
use Paysera\Pagination\Entity\OrderingConfiguration;
9
use Paysera\Pagination\Entity\ODM\ConfiguredQuery;
10
use Paysera\Pagination\Entity\Pager;
11
use Paysera\Pagination\Entity\Result;
12
use Paysera\Pagination\Service\CursorBuilderInterface;
13
14
class ResultProvider
15
{
16
    private $queryAnalyser;
17
    private $cursorBuilder;
18
19
    public function __construct(QueryAnalyser $queryAnalyser, CursorBuilderInterface $cursorBuilder)
20
    {
21
        $this->queryAnalyser = $queryAnalyser;
22
        $this->cursorBuilder = $cursorBuilder;
23
    }
24
25 View Code Duplication
    public function getResultForQuery(ConfiguredQuery $configuredQuery, Pager $pager): Result
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
26
    {
27
        $analysedQuery = $this->queryAnalyser->analyseQuery($configuredQuery, $pager);
28
29
        $result = $this->buildResult($analysedQuery, $pager);
30
31
        if ($configuredQuery->getItemTransformer() !== null) {
32
            $this->transformResultItems($configuredQuery->getItemTransformer(), $result);
33
        }
34
35
        if ($configuredQuery->isTotalCountNeeded()) {
36
            $totalCount = $this->calculateTotalCount($pager, count($result->getItems()));
37
            if ($totalCount === null) {
38
                $totalCount = $this->findCount($analysedQuery);
39
            }
40
            $result->setTotalCount($totalCount);
41
        }
42
43
        return $result;
44
    }
45
46
//    public function getTotalCountForQuery(ConfiguredQuery $configuredQuery): int
47
//    {
48
//        $analysedQuery = $this->queryAnalyser->analyseQueryWithoutPager($configuredQuery);
49
//
50
//        return $this->findCount($analysedQuery);
51
//    }
52
53 View Code Duplication
    private function buildResult(AnalysedQuery $analysedQuery, Pager $pager)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
54
    {
55
        $items = $this->findItems($analysedQuery, $pager);
56
57
        if (count($items) === 0) {
58
            return $this->buildResultForEmptyItems($analysedQuery, $pager);
59
        }
60
61
        $orderingConfigurations = $analysedQuery->getOrderingConfigurations();
62
        $previousCursor = $this->cursorBuilder->getCursorFromItem($items[0], $orderingConfigurations);
63
        $nextCursor = $this->cursorBuilder->getCursorFromItem($items[count($items) - 1], $orderingConfigurations);
64
65
        return (new Result())
66
            ->setItems($items)
67
            ->setPreviousCursor($previousCursor)
68
            ->setNextCursor($nextCursor)
69
            ->setHasPrevious($this->existsBeforeCursor($previousCursor, $analysedQuery))
70
            ->setHasNext($this->existsAfterCursor($nextCursor, $analysedQuery))
71
        ;
72
    }
73
74 View Code Duplication
    private function findItems(AnalysedQuery $analysedQuery, Pager $pager)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
75
    {
76
        $pagedQueryBuilder = $this->pageQueryBuilder($analysedQuery, $pager);
77
        $query = $pagedQueryBuilder->getQuery();
78
        $items = $query->execute()->toArray();
79
80
        if ($pager->getBefore() !== null) {
81
            return array_reverse($items);
82
        }
83
84
        return $items;
85
    }
86
87 View Code Duplication
    private function pageQueryBuilder(AnalysedQuery $analysedQuery, Pager $pager)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
88
    {
89
        $queryBuilder = $analysedQuery->cloneQueryBuilder();
90
91
        if ($pager->getLimit() !== null) {
92
            $queryBuilder->limit($pager->getLimit());
93
        }
94
95
        $orderingConfigurations = $analysedQuery->getOrderingConfigurations();
96
        if ($pager->getBefore() !== null) {
97
            $orderingConfigurations = $this->reverseOrderingDirection($orderingConfigurations);
98
        }
99
100
        $this->applyOrdering($queryBuilder, $orderingConfigurations);
101
102
        if ($pager->getOffset() !== null) {
103
            $this->applyOffset($queryBuilder, $pager->getOffset());
104
        } elseif ($pager->getBefore() !== null) {
105
            $this->applyBefore($queryBuilder, $pager->getBefore(), $analysedQuery);
106
        } elseif ($pager->getAfter() !== null) {
107
            $this->applyAfter($queryBuilder, $pager->getAfter(), $analysedQuery);
108
        }
109
110
        return $queryBuilder;
111
    }
112
113
    /**
114
     * @param QueryBuilder $queryBuilder
115
     * @param array|OrderingConfiguration[] $orderingConfigurations
116
     */
117
    private function applyOrdering(QueryBuilder $queryBuilder, array $orderingConfigurations)
118
    {
119
        foreach ($orderingConfigurations as $orderingConfiguration) {
120
            $queryBuilder->sort(
121
                $orderingConfiguration->getOrderByExpression(),
122
                $orderingConfiguration->isOrderAscending() ? 'ASC' : 'DESC'
123
            );
124
        }
125
    }
126
127
    private function applyOffset(QueryBuilder $queryBuilder, int $offset)
128
    {
129
        $queryBuilder->skip($offset);
130
    }
131
132
    /**
133
     * @param QueryBuilder $queryBuilder
134
     * @param string $after
135
     * @param AnalysedQuery $analysedQuery
136
     */
137
    private function applyAfter(QueryBuilder $queryBuilder, string $after, AnalysedQuery $analysedQuery)
138
    {
139
        $this->applyCursor($queryBuilder, $after, $analysedQuery, false);
140
    }
141
142
    /**
143
     * @param QueryBuilder $queryBuilder
144
     * @param string $after
145
     * @param AnalysedQuery $analysedQuery
146
     */
147
    private function applyBefore(QueryBuilder $queryBuilder, string $after, AnalysedQuery $analysedQuery)
148
    {
149
        $this->applyCursor($queryBuilder, $after, $analysedQuery, true);
150
    }
151
152
    /**
153
     * @param QueryBuilder $queryBuilder
154
     * @param string $cursor
155
     * @param AnalysedQuery $analysedQuery
156
     * @param bool $invert
157
     */
158
    private function applyCursor(QueryBuilder $queryBuilder, string $cursor, AnalysedQuery $analysedQuery, bool $invert)
159
    {
160
        $orderingConfigurations = $analysedQuery->getOrderingConfigurations();
161
        $parsedCursor = $this->cursorBuilder->parseCursor($cursor, count($orderingConfigurations));
162
163
        $whereClause = $queryBuilder->expr();
164
        $previousConditions = $queryBuilder->expr();
165
        foreach ($orderingConfigurations as $index => $orderingConfiguration) {
166
            $useLargerThan = $orderingConfiguration->isOrderAscending();
167
            if ($invert) {
168
                $useLargerThan = !$useLargerThan;
169
            }
170
            $sign = $useLargerThan ? '>' : '<';
171
            if ($parsedCursor->isCursoredItemIncluded() && $index === count($orderingConfigurations) - 1) {
172
                $sign .= '=';
173
            }
174
175
            $value = $parsedCursor->getElementAtIndex($index);
176
            $currentCondition = clone $previousConditions;
177
            $currentCondition->field($orderingConfiguration->getOrderByExpression());
178
            switch ($sign) {
179
                case '>':
180
                    $currentCondition->gt($value);
181
                    break;
182
                case '>=':
183
                    $currentCondition->gte($value);
184
                    break;
185
                case '<':
186
                    $currentCondition->lt($value);
187
                    break;
188
                case '<=':
189
                    $currentCondition->lte($value);
190
                    break;
191
            }
192
193
            $whereClause->addOr($currentCondition);
194
195
            $previousConditions->field($orderingConfiguration->getOrderByExpression())->equals($value);
196
        }
197
198
        $queryBuilder->addAnd($whereClause);
199
    }
200
201 View Code Duplication
    private function buildResultForEmptyItems(AnalysedQuery $analysedQuery, Pager $pager): Result
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
    {
203
        if ($pager->getLimit() === 0) {
204
            return $this->buildResultForZeroLimit($analysedQuery, $pager);
205
206
        } elseif ($pager->getBefore() !== null) {
207
            $nextCursor = $this->cursorBuilder->invertCursorInclusion($pager->getBefore());
208
            return (new Result())
209
                ->setPreviousCursor($pager->getBefore())
210
                ->setNextCursor($nextCursor)
211
                ->setHasPrevious(false)
212
                ->setHasNext($this->existsAfterCursor($nextCursor, $analysedQuery))
213
            ;
214
215
        } elseif ($pager->getAfter() !== null) {
216
            $previousCursor = $this->cursorBuilder->invertCursorInclusion($pager->getAfter());
217
            return (new Result())
218
                ->setPreviousCursor($previousCursor)
219
                ->setNextCursor($pager->getAfter())
220
                ->setHasPrevious($this->existsBeforeCursor($previousCursor, $analysedQuery))
221
                ->setHasNext(false)
222
            ;
223
224
        } elseif ($pager->getOffset() !== null && $pager->getOffset() > 0) {
225
            return $this->buildResultForTooLargeOffset($analysedQuery);
226
227
        }
228
229
        return (new Result())
230
            ->setHasPrevious(false)
231
            ->setHasNext(false)
232
        ;
233
    }
234
235 View Code Duplication
    private function buildResultForZeroLimit(AnalysedQuery $analysedQuery, Pager $zeroLimitPager): Result
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
    {
237
        $pager = (clone $zeroLimitPager)->setLimit(1);
238
        $items = $this->findItems($analysedQuery, $pager);
239
240
        if (count($items) === 0) {
241
            return $this->buildResultForEmptyItems($analysedQuery, $pager);
242
        }
243
244
        $orderingConfigurations = $analysedQuery->getOrderingConfigurations();
245
        $previousCursor = $this->cursorBuilder->getCursorFromItem($items[0], $orderingConfigurations);
246
        $nextCursor = $this->cursorBuilder->buildCursorWithIncludedItem($previousCursor);
247
248
        return (new Result())
249
            ->setPreviousCursor($previousCursor)
250
            ->setNextCursor($nextCursor)
251
            ->setHasPrevious($this->existsBeforeCursor($previousCursor, $analysedQuery))
252
            ->setHasNext(true)
253
        ;
254
    }
255
256 View Code Duplication
    private function buildResultForTooLargeOffset(AnalysedQuery $analysedQuery): Result
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
257
    {
258
        $result = (new Result())->setHasNext(false);
259
260
        $pagerForLastElement = (new Pager())->setLimit(1);
261
        $modifiedAnalysedQuery = (clone $analysedQuery)->setOrderingConfigurations(
262
            $this->reverseOrderingDirection($analysedQuery->getOrderingConfigurations())
263
        );
264
        $items = $this->findItems($modifiedAnalysedQuery, $pagerForLastElement);
265
        if (count($items) === 0) {
266
            return $result->setHasPrevious(false);
267
        }
268
269
        $lastItemCursor = $this->cursorBuilder->getCursorFromItem($items[0], $modifiedAnalysedQuery->getOrderingConfigurations());
270
        return $result
271
            ->setHasPrevious(true)
272
            ->setPreviousCursor($this->cursorBuilder->buildCursorWithIncludedItem($lastItemCursor))
273
            ->setNextCursor($lastItemCursor)
274
        ;
275
    }
276
277
    /**
278
     * @param array|OrderingConfiguration[] $orderingConfigurations
279
     * @return array|OrderingConfiguration[]
280
     */
281 View Code Duplication
    private function reverseOrderingDirection(array $orderingConfigurations): array
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283
        $reversedOrderingConfigurations = [];
284
        foreach ($orderingConfigurations as $orderingConfiguration) {
285
            $reversedOrderingConfigurations[] = (clone $orderingConfiguration)
286
                ->setOrderAscending(!$orderingConfiguration->isOrderAscending())
287
            ;
288
        }
289
        return $reversedOrderingConfigurations;
290
    }
291
292 View Code Duplication
    private function existsBeforeCursor(string $previousCursor, AnalysedQuery $analysedQuery)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
293
    {
294
        $nextPager = (new Pager())
295
            ->setBefore($previousCursor)
296
            ->setLimit(1)
297
        ;
298
299
        return count($this->findItems($analysedQuery, $nextPager)) > 0;
300
    }
301
302 View Code Duplication
    private function existsAfterCursor(string $nextCursor, AnalysedQuery $analysedQuery)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
303
    {
304
        $nextPager = (new Pager())
305
            ->setAfter($nextCursor)
306
            ->setLimit(1)
307
        ;
308
309
        return count($this->findItems($analysedQuery, $nextPager)) > 0;
310
    }
311
312 View Code Duplication
    private function calculateTotalCount(Pager $filter, int $resultCount)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313
    {
314
        if (
315
            $filter->getOffset() !== null
316
            && ($filter->getLimit() === null || $resultCount < $filter->getLimit())
317
            && ($resultCount !== 0 || $filter->getOffset() === 0)
318
        ) {
319
            return $resultCount + $filter->getOffset();
320
        }
321
322
        return null;
323
    }
324
325
    private function findCount(AnalysedQuery $analysedQuery): int
326
    {
327
        $countQueryBuilder = $analysedQuery->cloneQueryBuilder();
328
        $countQueryBuilder->count();
329
        return $countQueryBuilder->getQuery()->execute();
330
    }
331
332 View Code Duplication
    private function transformResultItems(callable $transform, Result $result)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
333
    {
334
        $transformedItems = [];
335
        foreach ($result->getItems() as $item) {
336
            $transformedItems[] = $transform($item);
337
        }
338
339
        $result->setItems($transformedItems);
340
    }
341
}
342