Passed
Pull Request — main (#104)
by Tom
04:28 queued 01:40
created

ResolveEntityFactory   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 192
Duplicated Lines 0 %

Importance

Changes 10
Bugs 0 Features 1
Metric Value
eloc 114
c 10
b 0
f 1
dl 0
loc 192
rs 9.76
wmc 33

4 Methods

Rating   Name   Duplication   Size   Complexity  
B buildFilterArray() 0 36 10
A get() 0 18 1
A __construct() 0 5 1
F buildPagination() 0 111 21
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\Criteria\Filters as FiltersDef;
9
use ApiSkeletons\Doctrine\GraphQL\Event\FilterQueryBuilder;
10
use ApiSkeletons\Doctrine\GraphQL\Type\Entity;
11
use ApiSkeletons\Doctrine\QueryBuilder\Filter\Applicator;
12
use Closure;
13
use Doctrine\ORM\EntityManager;
14
use Doctrine\ORM\QueryBuilder;
15
use Doctrine\ORM\Tools\Pagination\Paginator;
16
use GraphQL\Type\Definition\ResolveInfo;
17
use League\Event\EventDispatcher;
18
19
use function base64_decode;
20
use function base64_encode;
21
use function implode;
22
23
class ResolveEntityFactory
24
{
25
    public function __construct(
26
        protected Config $config,
27
        protected EntityManager $entityManager,
28
        protected EventDispatcher $eventDispatcher,
29
    ) {
30
    }
31
32
    public function get(Entity $entity, string $eventName): Closure
33
    {
34
        return function ($objectValue, array $args, $context, ResolveInfo $info) use ($entity, $eventName) {
35
            $entityClass = $entity->getEntityClass();
36
37
            $queryBuilderFilter = (new Applicator($this->entityManager, $entityClass))
38
                ->setEntityAlias('entity');
39
            $queryBuilder       = $queryBuilderFilter($this->buildFilterArray($args['filter'] ?? []))
40
                ->select('entity');
41
42
            return $this->buildPagination(
43
                queryBuilder: $queryBuilder,
44
                aliasMap: $queryBuilderFilter->getEntityAliasMap(),
45
                eventName: $eventName,
46
                objectValue: $objectValue,
47
                args: $args,
48
                context: $context,
49
                info: $info,
50
            );
51
        };
52
    }
53
54
    /**
55
     * @param mixed[] $filterTypes
56
     *
57
     * @return mixed[]
58
     */
59
    private function buildFilterArray(array $filterTypes): array
60
    {
61
        $filterArray = [];
62
63
        foreach ($filterTypes as $field => $filters) {
64
            foreach ($filters as $filter => $value) {
65
                switch ($filter) {
66
                    case FiltersDef::CONTAINS:
67
                        $filterArray[$field . '|like'] = $value;
68
                        break;
69
                    case FiltersDef::STARTSWITH:
70
                        $filterArray[$field . '|startswith'] = $value;
71
                        break;
72
                    case FiltersDef::ENDSWITH:
73
                        $filterArray[$field . '|endswith'] = $value;
74
                        break;
75
                    case FiltersDef::ISNULL:
76
                        $filterArray[$field . '|isnull'] = 'true';
77
                        break;
78
                    case FiltersDef::BETWEEN:
79
                        $filterArray[$field . '|between'] = $value['from'] . ',' . $value['to'];
80
                        break;
81
                    case FiltersDef::IN:
82
                        $filterArray[$field . '|in'] = implode(',', $value);
83
                        break;
84
                    case FiltersDef::NOTIN:
85
                        $filterArray[$field . '|notin'] = implode(',', $value);
86
                        break;
87
                    default:
88
                        $filterArray[$field . '|' . $filter] = (string) $value;
89
                        break;
90
                }
91
            }
92
        }
93
94
        return $filterArray;
95
    }
96
97
    /**
98
     * @param mixed[] $filterTypes
99
     * @param mixed[] $aliasMap
100
     * @param mixed[] $args
101
     *
102
     * @return mixed[]
103
     */
104
    public function buildPagination(
0 ignored issues
show
introduced by
Method \ApiSkeletons\Doctrine\GraphQL\Resolve\ResolveEntityFactory::buildPagination() does not have parameter type hint nor @param annotation for its parameter $resolve.
Loading history...
105
        QueryBuilder $queryBuilder,
106
        array $aliasMap,
107
        string $eventName,
108
        ...$resolve,
109
    ): array {
110
        $first  = 0;
111
        $after  = 0;
112
        $last   = 0;
113
        $before = 0;
114
        $offset = 0;
115
        $index  = 0;
116
        $edges  = [];
117
118
        if (isset($resolve['args']['pagination'])) {
119
            foreach ($resolve['args']['pagination'] as $field => $value) {
120
                switch ($field) {
121
                    case 'first':
122
                        $first = $value;
123
                        break;
124
                    case 'after':
125
                        $after = (int) base64_decode($value, true) + 1;
126
                        break;
127
                    case 'last':
128
                        $last = $value;
129
                        break;
130
                    case 'before':
131
                        $before = (int) base64_decode($value, true);
132
                        break;
133
                }
134
            }
135
        }
136
137
        $limit         = $this->config->getLimit();
138
        $adjustedLimit = $first ?: $last ?: $limit;
139
        if ($adjustedLimit < $limit) {
140
            $limit = $adjustedLimit;
141
        }
142
143
        if ($after) {
144
            $offset = $after;
145
        } elseif ($before) {
146
            $offset = $before - $limit;
147
        }
148
149
        if ($offset < 0) {
150
            $limit += $offset;
151
            $offset = 0;
152
        }
153
154
        if ($offset) {
155
            $queryBuilder->setFirstResult($offset);
156
        }
157
158
        if ($limit) {
159
            $queryBuilder->setMaxResults($limit);
160
        }
161
162
        /**
163
         * Fire the event dispatcher using the passed event name.
164
         * Include all resolve variables.
165
         */
166
        $this->eventDispatcher->dispatch(
167
            new FilterQueryBuilder(
168
                $queryBuilder,
169
                $aliasMap,
170
                $eventName,
171
                ...$resolve,
172
            ),
173
        );
174
175
        $paginator = new Paginator($queryBuilder->getQuery());
176
        $itemCount = $paginator->count();
177
178
        // Rebuild paginator if needed
179
        if ($last && ! $before) {
180
            $offset = $itemCount - $last;
181
            $queryBuilder->setFirstResult($offset);
182
            $paginator = new Paginator($queryBuilder->getQuery());
183
        }
184
185
        $lastCursor  = base64_encode((string) 0);
186
        $firstCursor = null;
187
        foreach ($paginator->getQuery()->getResult() as $result) {
188
            $cursor = base64_encode((string) ($index + $offset));
189
190
            $edges[] = [
191
                'node' => $result,
192
                'cursor' => $cursor,
193
            ];
194
195
            $lastCursor = $cursor;
196
            if (! $firstCursor) {
197
                $firstCursor = $cursor;
198
            }
199
200
            $index++;
201
        }
202
203
        $endCursor   = $paginator->count() ? $paginator->count() - 1 : 0;
204
        $startCursor = base64_encode((string) 0);
205
        $endCursor   = base64_encode((string) $endCursor);
206
207
        return [
208
            'edges' => $edges,
209
            'totalCount' => $paginator->count(),
210
            'pageInfo' => [
211
                'endCursor' => $endCursor,
212
                'startCursor' => $startCursor,
213
                'hasNextPage' => $endCursor !== $lastCursor,
214
                'hasPreviousPage' => $firstCursor !== null && $startCursor !== $firstCursor,
215
            ],
216
        ];
217
    }
218
}
219