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

api-platform / core / 8482337140

29 Mar 2024 03:02PM UTC coverage: 66.767% (-0.007%) from 66.774%
8482337140

push

github

web-flow
fix(doctrine): stateOptions force resource class on collection (#6255)

fixes #6039

1 of 2 new or added lines in 1 file covered. (50.0%)

15 existing lines in 1 file now uncovered.

16396 of 24557 relevant lines covered (66.77%)

40.36 hits per line

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

79.17
/src/Serializer/AbstractCollectionNormalizer.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\Serializer;
15

16
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
17
use ApiPlatform\Metadata\Operation;
18
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
19
use ApiPlatform\Metadata\ResourceClassResolverInterface;
20
use ApiPlatform\State\Pagination\PaginatorInterface;
21
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
22
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
23
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
24
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
25
use Symfony\Component\Serializer\Serializer;
26

27
/**
28
 * Base collection normalizer.
29
 *
30
 * @author Baptiste Meyer <baptiste.meyer@gmail.com>
31
 */
32
abstract class AbstractCollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface
33
{
34
    use ContextTrait {
35
        initContext as protected;
36
    }
37
    use NormalizerAwareTrait;
38
    use OperationContextTrait;
39

40
    /**
41
     * This constant must be overridden in the child class.
42
     */
43
    // @noRector \Rector\Php81\Rector\ClassConst\FinalizePublicClassConstantRector
44
    public const FORMAT = 'to-override';
45

46
    public function __construct(protected ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, protected string $pageParameterName, protected ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null)
47
    {
48
    }
216✔
49

50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
54
    {
55
        return static::FORMAT === $format && is_iterable($data);
80✔
56
    }
57

58
    public function hasCacheableSupportsMethod(): bool
59
    {
60
        if (method_exists(Serializer::class, 'getSupportedTypes')) {
×
61
            trigger_deprecation(
×
62
                'api-platform/core',
×
63
                '3.1',
×
64
                'The "%s()" method is deprecated, use "getSupportedTypes()" instead.',
×
65
                __METHOD__
×
66
            );
×
67
        }
68

69
        return true;
×
70
    }
71

72
    public function getSupportedTypes(?string $format): array
73
    {
74
        /*
75
         * At this point, support anything that is_iterable(), i.e. array|Traversable
76
         * for non-objects, symfony uses 'native-'.\gettype($data) :
77
         * https://github.com/tucksaun/symfony/blob/400685a68b00b0932f8ef41096578872b643099c/src/Symfony/Component/Serializer/Serializer.php#L254
78
         */
79
        if (static::FORMAT === $format) {
124✔
80
            return [
96✔
81
                'native-array' => true,
96✔
82
                '\Traversable' => true,
96✔
83
            ];
96✔
84
        }
85

86
        return [];
124✔
87
    }
88

89
    /**
90
     * {@inheritdoc}
91
     *
92
     * @param iterable $object
93
     */
94
    public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
95
    {
96
        if (!isset($context['resource_class']) || isset($context['api_sub_level'])) {
116✔
97
            return $this->normalizeRawCollection($object, $format, $context);
16✔
98
        }
99

100
        $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class']);
100✔
101
        $collectionContext = $this->initContext($resourceClass, $context);
100✔
102
        $data = [];
100✔
103
        $paginationData = $this->getPaginationData($object, $collectionContext);
100✔
104

105
        $childContext = $this->createOperationContext($collectionContext, $resourceClass);
100✔
106
        if (isset($collectionContext['force_resource_class'])) {
100✔
NEW
107
            $childContext['force_resource_class'] = $collectionContext['force_resource_class'];
×
108
        }
109

110
        $itemsData = $this->getItemsData($object, $format, $childContext);
100✔
111

112
        return array_merge_recursive($data, $paginationData, $itemsData);
96✔
113
    }
114

115
    /**
116
     * Normalizes a raw collection (not API resources).
117
     */
118
    protected function normalizeRawCollection(iterable $object, ?string $format = null, array $context = []): array|\ArrayObject
119
    {
120
        if (!$object && ($context[Serializer::EMPTY_ARRAY_AS_OBJECT] ?? false) && \is_array($object)) {
16✔
121
            return new \ArrayObject();
×
122
        }
123

124
        $data = [];
16✔
125
        foreach ($object as $index => $obj) {
16✔
126
            $data[$index] = $this->normalizer->normalize($obj, $format, $context);
12✔
127
        }
128

129
        return $data;
16✔
130
    }
131

132
    /**
133
     * Gets the pagination configuration.
134
     */
135
    protected function getPaginationConfig(iterable $object, array $context = []): array
136
    {
137
        $currentPage = $lastPage = $itemsPerPage = $pageTotalItems = $totalItems = null;
40✔
138
        $paginated = $paginator = false;
40✔
139

140
        if ($object instanceof PartialPaginatorInterface) {
40✔
141
            $paginated = $paginator = true;
24✔
142
            if ($object instanceof PaginatorInterface) {
24✔
143
                $paginated = 1. !== $lastPage = $object->getLastPage();
16✔
144
                $totalItems = $object->getTotalItems();
16✔
145
            } else {
146
                $pageTotalItems = (float) \count($object);
8✔
147
            }
148

149
            $currentPage = $object->getCurrentPage();
24✔
150
            $itemsPerPage = $object->getItemsPerPage();
24✔
151
        } elseif (is_countable($object)) {
16✔
152
            $totalItems = \count($object);
12✔
153
        }
154

155
        return [$paginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems];
40✔
156
    }
157

158
    protected function getOperation(array $context = []): Operation
159
    {
160
        $metadata = $this->resourceMetadataFactory->create($context['resource_class'] ?? '');
28✔
161

162
        return $metadata->getOperation($context['operation_name'] ?? null);
28✔
163
    }
164

165
    /**
166
     * Gets the pagination data.
167
     */
168
    abstract protected function getPaginationData(iterable $object, array $context = []): array;
169

170
    /**
171
     * Gets items data.
172
     */
173
    abstract protected function getItemsData(iterable $object, ?string $format = null, array $context = []): array;
174
}
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

© 2026 Coveralls, Inc