Passed
Pull Request — main (#89)
by Tom
03:33 queued 39s
created

ResolveEntityFactory::buildPagination()   F

Complexity

Conditions 20
Paths 6912

Size

Total Lines 116
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 73
c 0
b 0
f 0
nc 6912
nop 8
dl 0
loc 116
rs 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ApiSkeletons\Doctrine\GraphQL\Resolve;
6
7
use ApiSkeletons\Doctrine\GraphQL\Config;
8
use ApiSkeletons\Doctrine\GraphQL\Event\FilterQueryBuilder;
9
use ApiSkeletons\Doctrine\GraphQL\Type\Entity;
10
use ApiSkeletons\Doctrine\QueryBuilder\Filter\Applicator;
11
use Closure;
12
use Doctrine\ORM\EntityManager;
13
use Doctrine\ORM\QueryBuilder;
14
use Doctrine\ORM\Tools\Pagination\Paginator;
15
use GraphQL\Type\Definition\ResolveInfo;
16
use League\Event\EventDispatcher;
17
18
use function base64_decode;
19
use function base64_encode;
20
use function implode;
21
use function strrpos;
22
use function substr;
23
24
class ResolveEntityFactory
25
{
26
    public function __construct(
27
        protected Config $config,
28
        protected EntityManager $entityManager,
29
        protected EventDispatcher $eventDispatcher,
30
    ) {
31
    }
32
33
    public function get(Entity $entity, string $eventName): Closure
34
    {
35
        return function ($objectValue, array $args, $context, ResolveInfo $info) use ($entity, $eventName) {
36
            $entityClass = $entity->getEntityClass();
37
            // Resolve top level filters
38
            $filterTypes = $args['filter'] ?? [];
39
40
            $queryBuilderFilter = (new Applicator($this->entityManager, $entityClass))
41
                ->setEntityAlias('entity');
42
            $queryBuilder       = $queryBuilderFilter($this->buildFilterArray($filterTypes))
43
                ->select('entity');
44
45
            return $this->buildPagination(
46
                filterTypes: $filterTypes,
47
                queryBuilder: $queryBuilder,
48
                aliasMap: $queryBuilderFilter->getEntityAliasMap(),
49
                eventName: $eventName,
50
                objectValue: $objectValue,
51
                args: $args,
52
                context: $context,
53
                info: $info,
54
            );
55
        };
56
    }
57
58
    /**
59
     * @param mixed[] $filterTypes
60
     *
61
     * @return mixed[]
62
     */
63
    private function buildFilterArray(array $filterTypes): array
64
    {
65
        $filterArray = [];
66
67
        foreach ($filterTypes as $field => $value) {
68
            // Pagination is handled elsewhere
69
            switch ($field) {
70
                case '_first':
71
                case '_after':
72
                case '_last':
73
                case '_before':
74
                    continue 2;
75
            }
76
77
            // Handle other fields as $field_$type: $value
78
            // Get right-most _text
79
            $filter = substr($field, strrpos($field, '_') + 1);
80
81
            // Special case for eq `field: value`
82
            if (strrpos($field, '_') === false) {
83
                // Handle field:value
84
                $filterArray[$field . '|eq'] = (string) $value;
85
            } else {
86
                $field = substr($field, 0, strrpos($field, '_'));
87
88
                switch ($filter) {
89
                    case 'contains':
90
                        $filterArray[$field . '|like'] = $value;
91
                        break;
92
                    case 'startswith':
93
                        $filterArray[$field . '|startswith'] = $value;
94
                        break;
95
                    case 'endswith':
96
                        $filterArray[$field . '|endswith'] = $value;
97
                        break;
98
                    case 'isnull':
99
                        $filterArray[$field . '|isnull'] = 'true';
100
                        break;
101
                    case 'between':
102
                        $filterArray[$field . '|between'] = $value['from'] . ',' . $value['to'];
103
                        break;
104
                    case 'in':
105
                        $filterArray[$field . '|in'] = implode(',', $value);
106
                        break;
107
                    case 'notin':
108
                        $filterArray[$field . '|notin'] = implode(',', $value);
109
                        break;
110
                    default:
111
                        $filterArray[$field . '|' . $filter] = (string) $value;
112
                        break;
113
                }
114
            }
115
        }
116
117
        return $filterArray;
118
    }
119
120
    /**
121
     * @param mixed[] $filterTypes
122
     * @param mixed[] $aliasMap
123
     * @param mixed[] $args
124
     *
125
     * @return mixed[]
126
     */
127
    public function buildPagination(
128
        array $filterTypes,
129
        QueryBuilder $queryBuilder,
130
        array $aliasMap,
131
        string $eventName,
132
        mixed $objectValue,
133
        array $args,
134
        mixed $context,
135
        ResolveInfo $info,
136
    ): array {
137
        $first  = 0;
138
        $after  = 0;
139
        $last   = 0;
140
        $before = 0;
141
        $offset = 0;
142
143
        foreach ($filterTypes as $field => $value) {
144
            switch ($field) {
145
                case '_first':
146
                    $first = $value;
147
                    break;
148
                case '_after':
149
                    $after = (int) base64_decode($value, true) + 1;
150
                    break;
151
                case '_last':
152
                    $last = $value;
153
                    break;
154
                case '_before':
155
                    $before = (int) base64_decode($value, true);
156
                    break;
157
            }
158
        }
159
160
        $limit         = $this->config->getLimit();
161
        $adjustedLimit = $first ?: $last ?: $limit;
162
        if ($adjustedLimit < $limit) {
163
            $limit = $adjustedLimit;
164
        }
165
166
        if ($after) {
167
            $offset = $after;
168
        } elseif ($before) {
169
            $offset = $before - $limit;
170
        }
171
172
        if ($offset < 0) {
173
            $limit += $offset;
174
            $offset = 0;
175
        }
176
177
        if ($offset) {
178
            $queryBuilder->setFirstResult($offset);
179
        }
180
181
        if ($limit) {
182
            $queryBuilder->setMaxResults($limit);
183
        }
184
185
        /**
186
         * Fire the event dispatcher using the passed event name.
187
         * Include all resolve variables.
188
         */
189
190
        $this->eventDispatcher->dispatch(
191
            new FilterQueryBuilder(
192
                queryBuilder: $queryBuilder,
193
                entityAliasMap: $aliasMap,
194
                eventName: $eventName,
195
                objectValue: $objectValue,
196
                args: $args,
197
                context: $context,
198
                info: $info,
199
            ),
200
        );
201
202
        $paginator = new Paginator($queryBuilder->getQuery());
203
        $itemCount = $paginator->count();
204
205
        if ($last && ! $before) {
206
            $offset = $itemCount - $last;
207
            $queryBuilder->setFirstResult($offset);
208
            $paginator = new Paginator($queryBuilder->getQuery());
209
        }
210
211
        $edges       = [];
212
        $index       = 0;
213
        $lastCursor  = base64_encode((string) 0);
214
        $firstCursor = null;
215
        foreach ($paginator->getQuery()->getResult() as $result) {
216
            $cursor = base64_encode((string) ($index + $offset));
217
218
            $edges[] = [
219
                'node' => $result,
220
                'cursor' => $cursor,
221
            ];
222
223
            $lastCursor = $cursor;
224
            if (! $firstCursor) {
225
                $firstCursor = $cursor;
226
            }
227
228
            $index++;
229
        }
230
231
        $endCursor   = $paginator->count() ? $paginator->count() - 1 : 0;
232
        $startCursor = base64_encode((string) 0);
233
        $endCursor   = base64_encode((string) $endCursor);
234
235
        return [
236
            'edges' => $edges,
237
            'totalCount' => $paginator->count(),
238
            'pageInfo' => [
239
                'endCursor' => $endCursor,
240
                'startCursor' => $startCursor,
241
                'hasNextPage' => $endCursor !== $lastCursor,
242
                'hasPreviousPage' => $firstCursor !== null && $startCursor !== $firstCursor,
243
            ],
244
        ];
245
    }
246
}
247