• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 7735215006

01 Feb 2024 02:07AM UTC coverage: 61.704%. Remained the same
7735215006

Pull #6138

github

web-flow
chore(deps): bump codecov/codecov-action from 3 to 4

Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #6138: chore(deps): bump codecov/codecov-action from 3 to 4

15671 of 25397 relevant lines covered (61.7%)

30.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.95
/src/GraphQl/Resolver/Stage/SerializeStage.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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\GraphQl\Resolver\Stage;
15

16
use ApiPlatform\GraphQl\Resolver\Util\IdentifierTrait;
17
use ApiPlatform\GraphQl\Serializer\ItemNormalizer;
18
use ApiPlatform\GraphQl\Serializer\SerializerContextBuilderInterface;
19
use ApiPlatform\Metadata\CollectionOperationInterface;
20
use ApiPlatform\Metadata\GraphQl\Mutation;
21
use ApiPlatform\Metadata\GraphQl\Operation;
22
use ApiPlatform\Metadata\GraphQl\Subscription;
23
use ApiPlatform\State\Pagination\Pagination;
24
use ApiPlatform\State\Pagination\PaginatorInterface;
25
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
26
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
27

28
/**
29
 * Serialize stage of GraphQL resolvers.
30
 *
31
 * @author Alan Poulain <contact@alanpoulain.eu>
32
 */
33
final class SerializeStage implements SerializeStageInterface
34
{
35
    use IdentifierTrait;
36

37
    public function __construct(private readonly NormalizerInterface $normalizer, private readonly SerializerContextBuilderInterface $serializerContextBuilder, private readonly Pagination $pagination)
38
    {
39
    }
7✔
40

41
    public function __invoke(object|array|null $itemOrCollection, string $resourceClass, Operation $operation, array $context): ?array
42
    {
43
        $isCollection = $operation instanceof CollectionOperationInterface;
×
44
        $isMutation = $operation instanceof Mutation;
×
45
        $isSubscription = $operation instanceof Subscription;
×
46
        $shortName = $operation->getShortName();
×
47
        $operationName = $operation->getName();
×
48

49
        if (!($operation->canSerialize() ?? true)) {
×
50
            if ($isCollection) {
×
51
                if ($this->pagination->isGraphQlEnabled($operation, $context)) {
×
52
                    return 'cursor' === $this->pagination->getGraphQlPaginationType($operation) ?
×
53
                        $this->getDefaultCursorBasedPaginatedData() :
×
54
                        $this->getDefaultPageBasedPaginatedData();
×
55
                }
56

57
                return [];
×
58
            }
59

60
            if ($isMutation) {
×
61
                return $this->getDefaultMutationData($context);
×
62
            }
63

64
            if ($isSubscription) {
×
65
                return $this->getDefaultSubscriptionData($context);
×
66
            }
67

68
            return null;
×
69
        }
70

71
        $normalizationContext = $this->serializerContextBuilder->create($resourceClass, $operation, $context, true);
×
72

73
        $data = null;
×
74
        if (!$isCollection) {
×
75
            if ($isMutation && 'delete' === $operationName) {
×
76
                $data = ['id' => $this->getIdentifierFromContext($context)];
×
77
            } else {
78
                $data = $this->normalizer->normalize($itemOrCollection, ItemNormalizer::FORMAT, $normalizationContext);
×
79
            }
80
        }
81

82
        if ($isCollection && is_iterable($itemOrCollection)) {
×
83
            if (!$this->pagination->isGraphQlEnabled($operation, $context)) {
×
84
                $data = [];
×
85
                foreach ($itemOrCollection as $index => $object) {
×
86
                    $data[$index] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);
×
87
                }
88
            } else {
89
                $data = 'cursor' === $this->pagination->getGraphQlPaginationType($operation) ?
×
90
                    $this->serializeCursorBasedPaginatedCollection($itemOrCollection, $normalizationContext, $context) :
×
91
                    $this->serializePageBasedPaginatedCollection($itemOrCollection, $normalizationContext, $context);
×
92
            }
93
        }
94

95
        if (null !== $data && !\is_array($data)) {
×
96
            throw new \UnexpectedValueException('Expected serialized data to be a nullable array.');
×
97
        }
98

99
        if ($isMutation || $isSubscription) {
×
100
            $wrapFieldName = lcfirst($shortName);
×
101

102
            return [$wrapFieldName => $data] + ($isMutation ? $this->getDefaultMutationData($context) : $this->getDefaultSubscriptionData($context));
×
103
        }
104

105
        return $data;
×
106
    }
107

108
    /**
109
     * @throws \LogicException
110
     * @throws \UnexpectedValueException
111
     */
112
    private function serializeCursorBasedPaginatedCollection(iterable $collection, array $normalizationContext, array $context): array
113
    {
114
        $args = $context['args'];
×
115

116
        if (!($collection instanceof PartialPaginatorInterface)) {
×
117
            throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s or %s.', PaginatorInterface::class, PartialPaginatorInterface::class));
×
118
        }
119

120
        $selection = $context['info']->getFieldSelection(1);
×
121

122
        $offset = 0;
×
123
        $totalItems = 1; // For partial pagination, always consider there is at least one item.
×
124
        $data = ['edges' => []];
×
125
        if (isset($selection['pageInfo']) || isset($selection['totalCount']) || isset($selection['edges']['cursor'])) {
×
126
            $nbPageItems = $collection->count();
×
127
            if (isset($args['after'])) {
×
128
                $after = base64_decode($args['after'], true);
×
129
                if (false === $after || '' === $args['after']) {
×
130
                    throw new \UnexpectedValueException('' === $args['after'] ? 'Empty cursor is invalid' : sprintf('Cursor %s is invalid', $args['after']));
×
131
                }
132
                $offset = 1 + (int) $after;
×
133
            }
134

135
            if ($collection instanceof PaginatorInterface && (isset($selection['pageInfo']) || isset($selection['totalCount']))) {
×
136
                $totalItems = $collection->getTotalItems();
×
137
                if (isset($args['before'])) {
×
138
                    $before = base64_decode($args['before'], true);
×
139
                    if (false === $before || '' === $args['before']) {
×
140
                        throw new \UnexpectedValueException('' === $args['before'] ? 'Empty cursor is invalid' : sprintf('Cursor %s is invalid', $args['before']));
×
141
                    }
142
                    $offset = (int) $before - $nbPageItems;
×
143
                }
144
                if (isset($args['last']) && !isset($args['before'])) {
×
145
                    $offset = $totalItems - $args['last'];
×
146
                }
147
            }
148

149
            $offset = max(0, $offset);
×
150

151
            $data = $this->getDefaultCursorBasedPaginatedData();
×
152
            if ((isset($selection['pageInfo']) || isset($selection['totalCount'])) && $totalItems > 0) {
×
153
                isset($selection['pageInfo']['startCursor']) && $data['pageInfo']['startCursor'] = base64_encode((string) $offset);
×
154
                $end = $offset + $nbPageItems - 1;
×
155
                isset($selection['pageInfo']['endCursor']) && $data['pageInfo']['endCursor'] = base64_encode((string) max($end, 0));
×
156
                isset($selection['pageInfo']['hasPreviousPage']) && $data['pageInfo']['hasPreviousPage'] = $offset > 0;
×
157
                if ($collection instanceof PaginatorInterface) {
×
158
                    isset($selection['totalCount']) && $data['totalCount'] = $totalItems;
×
159

160
                    $itemsPerPage = $collection->getItemsPerPage();
×
161
                    isset($selection['pageInfo']['hasNextPage']) && $data['pageInfo']['hasNextPage'] = (float) ($itemsPerPage > 0 ? $offset % $itemsPerPage : $offset) + $itemsPerPage * $collection->getCurrentPage() < $totalItems;
×
162
                }
163
            }
164
        }
165

166
        $index = 0;
×
167
        foreach ($collection as $object) {
×
168
            $edge = [
×
169
                'node' => $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext),
×
170
            ];
×
171
            if (isset($selection['edges']['cursor'])) {
×
172
                $edge['cursor'] = base64_encode((string) ($index + $offset));
×
173
            }
174
            $data['edges'][$index] = $edge;
×
175
            ++$index;
×
176
        }
177

178
        return $data;
×
179
    }
180

181
    /**
182
     * @throws \LogicException
183
     */
184
    private function serializePageBasedPaginatedCollection(iterable $collection, array $normalizationContext, array $context): array
185
    {
186
        $data = ['collection' => []];
×
187

188
        $selection = $context['info']->getFieldSelection(1);
×
189
        if (isset($selection['paginationInfo'])) {
×
190
            $data['paginationInfo'] = [];
×
191
            if (isset($selection['paginationInfo']['itemsPerPage'])) {
×
192
                if (!($collection instanceof PartialPaginatorInterface)) {
×
193
                    throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s to return itemsPerPage field.', PartialPaginatorInterface::class));
×
194
                }
195
                $data['paginationInfo']['itemsPerPage'] = $collection->getItemsPerPage();
×
196
            }
197
            if (isset($selection['paginationInfo']['totalCount'])) {
×
198
                if (!($collection instanceof PaginatorInterface)) {
×
199
                    throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s to return totalCount field.', PaginatorInterface::class));
×
200
                }
201
                $data['paginationInfo']['totalCount'] = $collection->getTotalItems();
×
202
            }
203
            if (isset($selection['paginationInfo']['lastPage'])) {
×
204
                if (!($collection instanceof PaginatorInterface)) {
×
205
                    throw new \LogicException(sprintf('Collection returned by the collection data provider must implement %s to return lastPage field.', PaginatorInterface::class));
×
206
                }
207
                $data['paginationInfo']['lastPage'] = $collection->getLastPage();
×
208
            }
209
        }
210

211
        foreach ($collection as $object) {
×
212
            $data['collection'][] = $this->normalizer->normalize($object, ItemNormalizer::FORMAT, $normalizationContext);
×
213
        }
214

215
        return $data;
×
216
    }
217

218
    private function getDefaultCursorBasedPaginatedData(): array
219
    {
220
        return ['totalCount' => 0., 'edges' => [], 'pageInfo' => ['startCursor' => null, 'endCursor' => null, 'hasNextPage' => false, 'hasPreviousPage' => false]];
×
221
    }
222

223
    private function getDefaultPageBasedPaginatedData(): array
224
    {
225
        return ['collection' => [], 'paginationInfo' => ['itemsPerPage' => 0., 'totalCount' => 0., 'lastPage' => 0.]];
×
226
    }
227

228
    private function getDefaultMutationData(array $context): array
229
    {
230
        return ['clientMutationId' => $context['args']['input']['clientMutationId'] ?? null];
×
231
    }
232

233
    private function getDefaultSubscriptionData(array $context): array
234
    {
235
        return ['clientSubscriptionId' => $context['args']['input']['clientSubscriptionId'] ?? null];
×
236
    }
237
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc