Passed
Pull Request — main (#94)
by Tom
03:24
created

src/Resolve/ResolveEntityFactory.php (1 issue)

Severity
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
use function is_array;
0 ignored issues
show
Type is_array is not used in this file.
Loading history...
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
38
            $queryBuilderFilter = (new Applicator($this->entityManager, $entityClass))
39
                ->setEntityAlias('entity');
40
            $queryBuilder       = $queryBuilderFilter($this->buildFilterArray($args['filter'] ?? []))
41
                ->select('entity');
42
43
            return $this->buildPagination(
44
                queryBuilder: $queryBuilder,
45
                aliasMap: $queryBuilderFilter->getEntityAliasMap(),
46
                eventName: $eventName,
47
                objectValue: $objectValue,
48
                args: $args,
49
                context: $context,
50
                info: $info,
51
            );
52
        };
53
    }
54
55
    /**
56
     * @param mixed[] $filterTypes
57
     *
58
     * @return mixed[]
59
     */
60
    private function buildFilterArray(array $filterTypes): array
61
    {
62
        $filterArray = [];
63
64
        foreach ($filterTypes as $field => $filters) {
65
            foreach ($filters as $filter => $value) {
66
                switch ($filter) {
67
                    case FiltersDef::CONTAINS:
68
                        $filterArray[$field . '|like'] = $value;
69
                        break;
70
                    case FiltersDef::STARTSWITH:
71
                        $filterArray[$field . '|startswith'] = $value;
72
                        break;
73
                    case FiltersDef::ENDSWITH:
74
                        $filterArray[$field . '|endswith'] = $value;
75
                        break;
76
                    case FiltersDef::ISNULL:
77
                        $filterArray[$field . '|isnull'] = 'true';
78
                        break;
79
                    case FiltersDef::BETWEEN:
80
                        $filterArray[$field . '|between'] = $value['from'] . ',' . $value['to'];
81
                        break;
82
                    case FiltersDef::IN:
83
                        $filterArray[$field . '|in'] = implode(',', $value);
84
                        break;
85
                    case FiltersDef::NOTIN:
86
                        $filterArray[$field . '|notin'] = implode(',', $value);
87
                        break;
88
                    default:
89
                        $filterArray[$field . '|' . $filter] = (string) $value;
90
                        break;
91
                }
92
            }
93
        }
94
95
        return $filterArray;
96
    }
97
98
    /**
99
     * @param mixed[] $filterTypes
100
     * @param mixed[] $aliasMap
101
     * @param mixed[] $args
102
     *
103
     * @return mixed[]
104
     */
105
    public function buildPagination(
106
        QueryBuilder $queryBuilder,
107
        array $aliasMap,
108
        string $eventName,
109
        mixed $objectValue,
110
        array $args,
111
        mixed $context,
112
        ResolveInfo $info,
113
    ): array {
114
        $first  = 0;
115
        $after  = 0;
116
        $last   = 0;
117
        $before = 0;
118
        $offset = 0;
119
120
        if (isset($args['pagination'])) {
121
            foreach ($args['pagination'] as $field => $value) {
122
                switch ($field) {
123
                    case 'first':
124
                        $first = $value;
125
                        break;
126
                    case 'after':
127
                        $after = (int) base64_decode($value, true) + 1;
128
                        break;
129
                    case 'last':
130
                        $last = $value;
131
                        break;
132
                    case 'before':
133
                        $before = (int) base64_decode($value, true);
134
                        break;
135
                }
136
            }
137
        }
138
139
        $limit         = $this->config->getLimit();
140
        $adjustedLimit = $first ?: $last ?: $limit;
141
        if ($adjustedLimit < $limit) {
142
            $limit = $adjustedLimit;
143
        }
144
145
        if ($after) {
146
            $offset = $after;
147
        } elseif ($before) {
148
            $offset = $before - $limit;
149
        }
150
151
        if ($offset < 0) {
152
            $limit += $offset;
153
            $offset = 0;
154
        }
155
156
        if ($offset) {
157
            $queryBuilder->setFirstResult($offset);
158
        }
159
160
        if ($limit) {
161
            $queryBuilder->setMaxResults($limit);
162
        }
163
164
        /**
165
         * Fire the event dispatcher using the passed event name.
166
         * Include all resolve variables.
167
         */
168
169
        $this->eventDispatcher->dispatch(
170
            new FilterQueryBuilder(
171
                queryBuilder: $queryBuilder,
172
                entityAliasMap: $aliasMap,
173
                eventName: $eventName,
174
                objectValue: $objectValue,
175
                args: $args,
176
                context: $context,
177
                info: $info,
178
            ),
179
        );
180
181
        $paginator = new Paginator($queryBuilder->getQuery());
182
        $itemCount = $paginator->count();
183
184
        if ($last && ! $before) {
185
            $offset = $itemCount - $last;
186
            $queryBuilder->setFirstResult($offset);
187
            $paginator = new Paginator($queryBuilder->getQuery());
188
        }
189
190
        $edges       = [];
191
        $index       = 0;
192
        $lastCursor  = base64_encode((string) 0);
193
        $firstCursor = null;
194
        foreach ($paginator->getQuery()->getResult() as $result) {
195
            $cursor = base64_encode((string) ($index + $offset));
196
197
            $edges[] = [
198
                'node' => $result,
199
                'cursor' => $cursor,
200
            ];
201
202
            $lastCursor = $cursor;
203
            if (! $firstCursor) {
204
                $firstCursor = $cursor;
205
            }
206
207
            $index++;
208
        }
209
210
        $endCursor   = $paginator->count() ? $paginator->count() - 1 : 0;
211
        $startCursor = base64_encode((string) 0);
212
        $endCursor   = base64_encode((string) $endCursor);
213
214
        return [
215
            'edges' => $edges,
216
            'totalCount' => $paginator->count(),
217
            'pageInfo' => [
218
                'endCursor' => $endCursor,
219
                'startCursor' => $startCursor,
220
                'hasNextPage' => $endCursor !== $lastCursor,
221
                'hasPreviousPage' => $firstCursor !== null && $startCursor !== $firstCursor,
222
            ],
223
        ];
224
    }
225
}
226