Completed
Push — master ( dee4c0...5b2184 )
by Han Hui
21s queued 13s
created

SerializeStage   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 183
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 96
dl 0
loc 183
rs 8.8
c 1
b 0
f 0
wmc 45

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A serializePageBasedPaginatedCollection() 0 16 3
A getDefaultSubscriptionData() 0 3 1
D __invoke() 0 64 20
A getDefaultPageBasedPaginatedData() 0 3 1
A getDefaultMutationData() 0 3 1
A getDefaultCursorBasedPaginatedData() 0 3 1
C serializeCursorBasedPaginatedCollection() 0 52 17

How to fix   Complexity   

Complex Class

Complex classes like SerializeStage 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.

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 SerializeStage, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace ApiPlatform\Core\GraphQl\Resolver\Stage;
15
16
use ApiPlatform\Core\DataProvider\Pagination;
17
use ApiPlatform\Core\DataProvider\PaginatorInterface;
18
use ApiPlatform\Core\GraphQl\Resolver\Util\IdentifierTrait;
19
use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer;
20
use ApiPlatform\Core\GraphQl\Serializer\SerializerContextBuilderInterface;
21
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
22
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
23
24
/**
25
 * Serialize stage of GraphQL resolvers.
26
 *
27
 * @experimental
28
 *
29
 * @author Alan Poulain <[email protected]>
30
 */
31
final class SerializeStage implements SerializeStageInterface
32
{
33
    use IdentifierTrait;
34
35
    private $resourceMetadataFactory;
36
    private $normalizer;
37
    private $serializerContextBuilder;
38
    private $pagination;
39
40
    public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, NormalizerInterface $normalizer, SerializerContextBuilderInterface $serializerContextBuilder, Pagination $pagination)
41
    {
42
        $this->resourceMetadataFactory = $resourceMetadataFactory;
43
        $this->normalizer = $normalizer;
44
        $this->serializerContextBuilder = $serializerContextBuilder;
45
        $this->pagination = $pagination;
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public function __invoke($itemOrCollection, string $resourceClass, string $operationName, array $context): ?array
52
    {
53
        $isCollection = $context['is_collection'];
54
        $isMutation = $context['is_mutation'];
55
        $isSubscription = $context['is_subscription'];
56
57
        $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass);
58
        if (!$resourceMetadata->getGraphqlAttribute($operationName, 'serialize', true, true)) {
59
            if ($isCollection) {
60
                if ($this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) {
61
                    return 'cursor' === $this->pagination->getGraphQlPaginationType($resourceClass, $operationName) ?
62
                        $this->getDefaultCursorBasedPaginatedData() :
63
                        $this->getDefaultPageBasedPaginatedData();
64
                }
65
66
                return [];
67
            }
68
69
            if ($isMutation) {
70
                return $this->getDefaultMutationData($context);
71
            }
72
73
            if ($isSubscription) {
74
                return $this->getDefaultSubscriptionData($context);
75
            }
76
77
            return null;
78
        }
79
80
        $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operationName, $context, true);
81
82
        $data = null;
83
        if (!$isCollection) {
84
            if ($isMutation && 'delete' === $operationName) {
85
                $data = ['id' => $this->getIdentifierFromContext($context)];
86
            } else {
87
                $data = $this->normalizer->normalize($itemOrCollection, ItemNormalizer::FORMAT, $normalizationContext);
88
            }
89
        }
90
91
        if ($isCollection && is_iterable($itemOrCollection)) {
92
            if (!$this->pagination->isGraphQlEnabled($resourceClass, $operationName, $context)) {
93
                $data = [];
94
                foreach ($itemOrCollection as $index => $object) {
95
                    $data[$index] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);
96
                }
97
            } else {
98
                $data = 'cursor' === $this->pagination->getGraphQlPaginationType($resourceClass, $operationName) ?
99
                    $this->serializeCursorBasedPaginatedCollection($itemOrCollection, $normalizationContext, $context) :
100
                    $this->serializePageBasedPaginatedCollection($itemOrCollection, $normalizationContext);
101
            }
102
        }
103
104
        if (null !== $data && !\is_array($data)) {
105
            throw new \UnexpectedValueException('Expected serialized data to be a nullable array.');
106
        }
107
108
        if ($isMutation || $isSubscription) {
109
            $wrapFieldName = lcfirst($resourceMetadata->getShortName());
110
111
            return [$wrapFieldName => $data] + ($isMutation ? $this->getDefaultMutationData($context) : $this->getDefaultSubscriptionData($context));
112
        }
113
114
        return $data;
115
    }
116
117
    /**
118
     * @throws \LogicException
119
     * @throws \UnexpectedValueException
120
     */
121
    private function serializeCursorBasedPaginatedCollection(iterable $collection, array $normalizationContext, array $context): array
122
    {
123
        $args = $context['args'];
124
125
        if (!($collection instanceof PaginatorInterface)) {
126
            throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s.', PaginatorInterface::class));
127
        }
128
129
        $offset = 0;
130
        $totalItems = $collection->getTotalItems();
131
        $nbPageItems = $collection->count();
132
        if (isset($args['after'])) {
133
            $after = base64_decode($args['after'], true);
134
            if (false === $after || '' === $args['after']) {
135
                throw new \UnexpectedValueException('' === $args['after'] ? 'Empty cursor is invalid' : sprintf('Cursor %s is invalid', $args['after']));
136
            }
137
            $offset = 1 + (int) $after;
138
        }
139
        if (isset($args['before'])) {
140
            $before = base64_decode($args['before'], true);
141
            if (false === $before || '' === $args['before']) {
142
                throw new \UnexpectedValueException('' === $args['before'] ? 'Empty cursor is invalid' : sprintf('Cursor %s is invalid', $args['before']));
143
            }
144
            $offset = (int) $before - $nbPageItems;
145
        }
146
        if (isset($args['last']) && !isset($args['before'])) {
147
            $offset = $totalItems - $args['last'];
148
        }
149
        $offset = 0 > $offset ? 0 : $offset;
150
151
        $data = $this->getDefaultCursorBasedPaginatedData();
152
153
        if (($totalItems = $collection->getTotalItems()) > 0) {
154
            $data['totalCount'] = $totalItems;
155
            $data['pageInfo']['startCursor'] = base64_encode((string) $offset);
156
            $end = $offset + $nbPageItems - 1;
157
            $data['pageInfo']['endCursor'] = base64_encode((string) ($end >= 0 ? $end : 0));
158
            $itemsPerPage = $collection->getItemsPerPage();
159
            $data['pageInfo']['hasNextPage'] = (float) ($itemsPerPage > 0 ? $offset % $itemsPerPage : $offset) + $itemsPerPage * $collection->getCurrentPage() < $totalItems;
160
            $data['pageInfo']['hasPreviousPage'] = $offset > 0;
161
        }
162
163
        $index = 0;
164
        foreach ($collection as $object) {
165
            $data['edges'][$index] = [
166
                'node' => $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext),
167
                'cursor' => base64_encode((string) ($index + $offset)),
168
            ];
169
            ++$index;
170
        }
171
172
        return $data;
173
    }
174
175
    /**
176
     * @throws \LogicException
177
     */
178
    private function serializePageBasedPaginatedCollection(iterable $collection, array $normalizationContext): array
179
    {
180
        if (!($collection instanceof PaginatorInterface)) {
181
            throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s.', PaginatorInterface::class));
182
        }
183
184
        $data = $this->getDefaultPageBasedPaginatedData();
185
        $data['paginationInfo']['totalCount'] = $collection->getTotalItems();
186
        $data['paginationInfo']['lastPage'] = $collection->getLastPage();
187
        $data['paginationInfo']['itemsPerPage'] = $collection->getItemsPerPage();
188
189
        foreach ($collection as $object) {
190
            $data['collection'][] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);
191
        }
192
193
        return $data;
194
    }
195
196
    private function getDefaultCursorBasedPaginatedData(): array
197
    {
198
        return ['totalCount' => 0., 'edges' => [], 'pageInfo' => ['startCursor' => null, 'endCursor' => null, 'hasNextPage' => false, 'hasPreviousPage' => false]];
199
    }
200
201
    private function getDefaultPageBasedPaginatedData(): array
202
    {
203
        return ['collection' => [], 'paginationInfo' => ['itemsPerPage' => 0., 'totalCount' => 0., 'lastPage' => 0.]];
204
    }
205
206
    private function getDefaultMutationData(array $context): array
207
    {
208
        return ['clientMutationId' => $context['args']['input']['clientMutationId'] ?? null];
209
    }
210
211
    private function getDefaultSubscriptionData(array $context): array
212
    {
213
        return ['clientSubscriptionId' => $context['args']['input']['clientSubscriptionId'] ?? null];
214
    }
215
}
216