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

api-platform / core / 3713134090

pending completion
3713134090

Pull #5254

github

GitHub
Merge b2ec54b3c into ac711530f
Pull Request #5254: [OpenApi] Add ApiResource::openapi and deprecate openapiContext

197 of 197 new or added lines in 5 files covered. (100.0%)

7493 of 12362 relevant lines covered (60.61%)

67.56 hits per line

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

95.0
/src/Hydra/Serializer/PartialCollectionViewNormalizer.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\Hydra\Serializer;
15

16
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
17
use ApiPlatform\State\Pagination\PaginatorInterface;
18
use ApiPlatform\State\Pagination\PartialPaginatorInterface;
19
use ApiPlatform\Util\IriHelper;
20
use Symfony\Component\PropertyAccess\PropertyAccess;
21
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
22
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
23
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
24
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
25
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
26

27
/**
28
 * Adds a view key to the result of a paginated Hydra collection.
29
 *
30
 * @author Kévin Dunglas <dunglas@gmail.com>
31
 * @author Samuel ROZE <samuel.roze@gmail.com>
32
 */
33
final class PartialCollectionViewNormalizer implements NormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface
34
{
35
    private readonly PropertyAccessorInterface $propertyAccessor;
36

37
    public function __construct(private readonly NormalizerInterface $collectionNormalizer, private readonly string $pageParameterName = 'page', private string $enabledParameterName = 'pagination', private readonly ?ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory = null, PropertyAccessorInterface $propertyAccessor = null)
38
    {
39
        $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
649✔
40
    }
41

42
    /**
43
     * {@inheritdoc}
44
     */
45
    public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
46
    {
47
        $data = $this->collectionNormalizer->normalize($object, $format, $context);
221✔
48

49
        if (isset($context['api_sub_level'])) {
221✔
50
            return $data;
146✔
51
        }
52

53
        if (!\is_array($data)) {
155✔
54
            throw new UnexpectedValueException('Expected data to be an array');
×
55
        }
56

57
        $currentPage = $lastPage = $itemsPerPage = $pageTotalItems = null;
155✔
58
        if ($paginated = ($object instanceof PartialPaginatorInterface)) {
155✔
59
            if ($object instanceof PaginatorInterface) {
149✔
60
                $paginated = 1. !== $lastPage = $object->getLastPage();
149✔
61
            } else {
62
                $itemsPerPage = $object->getItemsPerPage();
×
63
                $pageTotalItems = (float) \count($object);
×
64
            }
65

66
            $currentPage = $object->getCurrentPage();
149✔
67
        }
68

69
        $parsed = IriHelper::parseIri($context['request_uri'] ?? '/', $this->pageParameterName);
155✔
70
        $appliedFilters = $parsed['parameters'];
155✔
71
        unset($appliedFilters[$this->enabledParameterName]);
155✔
72

73
        if (!$appliedFilters && !$paginated) {
155✔
74
            return $data;
48✔
75
        }
76

77
        $isPaginatedWithCursor = false;
107✔
78
        $cursorPaginationAttribute = null;
107✔
79
        if ($this->resourceMetadataFactory && isset($context['resource_class']) && $paginated) {
107✔
80
            $operation = $this->resourceMetadataFactory->create($context['resource_class'])->getOperation($context['operation_name'] ?? null);
63✔
81
            $isPaginatedWithCursor = [] !== $cursorPaginationAttribute = ($operation->getPaginationViaCursor() ?? []);
63✔
82
        }
83

84
        $data['hydra:view'] = ['@id' => null, '@type' => 'hydra:PartialCollectionView'];
107✔
85

86
        if ($isPaginatedWithCursor) {
107✔
87
            return $this->populateDataWithCursorBasedPagination($data, $parsed, $object, $cursorPaginationAttribute);
1✔
88
        }
89

90
        $data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $paginated ? $currentPage : null);
106✔
91

92
        if ($paginated) {
106✔
93
            return $this->populateDataWithPagination($data, $parsed, $currentPage, $lastPage, $itemsPerPage, $pageTotalItems);
62✔
94
        }
95

96
        return $data;
46✔
97
    }
98

99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function supportsNormalization(mixed $data, string $format = null, array $context = []): bool
103
    {
104
        return $this->collectionNormalizer->supportsNormalization($data, $format, $context);
516✔
105
    }
106

107
    public function hasCacheableSupportsMethod(): bool
108
    {
109
        return $this->collectionNormalizer instanceof CacheableSupportsMethodInterface && $this->collectionNormalizer->hasCacheableSupportsMethod();
516✔
110
    }
111

112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function setNormalizer(NormalizerInterface $normalizer): void
116
    {
117
        if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
649✔
118
            $this->collectionNormalizer->setNormalizer($normalizer);
649✔
119
        }
120
    }
121

122
    private function cursorPaginationFields(array $fields, int $direction, $object): array
123
    {
124
        $paginationFilters = [];
1✔
125

126
        foreach ($fields as $field) {
1✔
127
            $forwardRangeOperator = 'desc' === strtolower($field['direction']) ? 'lt' : 'gt';
1✔
128
            $backwardRangeOperator = 'gt' === $forwardRangeOperator ? 'lt' : 'gt';
1✔
129

130
            $operator = $direction > 0 ? $forwardRangeOperator : $backwardRangeOperator;
1✔
131

132
            $paginationFilters[$field['field']] = [
1✔
133
                $operator => (string) $this->propertyAccessor->getValue($object, $field['field']),
1✔
134
            ];
1✔
135
        }
136

137
        return $paginationFilters;
1✔
138
    }
139

140
    private function populateDataWithCursorBasedPagination(array $data, array $parsed, \Traversable $object, ?array $cursorPaginationAttribute): array
141
    {
142
        $objects = iterator_to_array($object);
1✔
143
        $firstObject = current($objects);
1✔
144
        $lastObject = end($objects);
1✔
145

146
        $data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters']);
1✔
147

148
        if (false !== $lastObject && \is_array($cursorPaginationAttribute)) {
1✔
149
            $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, 1, $lastObject)));
1✔
150
        }
151

152
        if (false !== $firstObject && \is_array($cursorPaginationAttribute)) {
1✔
153
            $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, -1, $firstObject)));
1✔
154
        }
155

156
        return $data;
1✔
157
    }
158

159
    private function populateDataWithPagination(array $data, array $parsed, ?float $currentPage, ?float $lastPage, ?float $itemsPerPage, ?float $pageTotalItems): array
160
    {
161
        if (null !== $lastPage) {
62✔
162
            $data['hydra:view']['hydra:first'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, 1.);
62✔
163
            $data['hydra:view']['hydra:last'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $lastPage);
62✔
164
        }
165

166
        if (1. !== $currentPage) {
62✔
167
            $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage - 1.);
4✔
168
        }
169

170
        if ((null !== $lastPage && $currentPage < $lastPage) || (null === $lastPage && $pageTotalItems >= $itemsPerPage)) {
62✔
171
            $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage + 1.);
61✔
172
        }
173

174
        return $data;
62✔
175
    }
176
}
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