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\Hydra\Serializer; |
||
15 | |||
16 | use ApiPlatform\Core\DataProvider\PaginatorInterface; |
||
17 | use ApiPlatform\Core\DataProvider\PartialPaginatorInterface; |
||
18 | use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; |
||
19 | use ApiPlatform\Core\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 <[email protected]> |
||
31 | * @author Samuel ROZE <[email protected]> |
||
32 | */ |
||
33 | final class PartialCollectionViewNormalizer implements NormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface |
||
34 | { |
||
35 | private $collectionNormalizer; |
||
36 | private $pageParameterName; |
||
37 | private $enabledParameterName; |
||
38 | private $resourceMetadataFactory; |
||
39 | private $propertyAccessor; |
||
40 | |||
41 | public function __construct(NormalizerInterface $collectionNormalizer, string $pageParameterName = 'page', string $enabledParameterName = 'pagination', ResourceMetadataFactoryInterface $resourceMetadataFactory = null, PropertyAccessorInterface $propertyAccessor = null) |
||
42 | { |
||
43 | $this->collectionNormalizer = $collectionNormalizer; |
||
44 | $this->pageParameterName = $pageParameterName; |
||
45 | $this->enabledParameterName = $enabledParameterName; |
||
46 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||
47 | $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * {@inheritdoc} |
||
52 | */ |
||
53 | public function normalize($object, $format = null, array $context = []) |
||
54 | { |
||
55 | $data = $this->collectionNormalizer->normalize($object, $format, $context); |
||
56 | if (!\is_array($data)) { |
||
57 | throw new UnexpectedValueException('Expected data to be an array'); |
||
58 | } |
||
59 | |||
60 | if (isset($context['api_sub_level'])) { |
||
61 | return $data; |
||
62 | } |
||
63 | |||
64 | $currentPage = $lastPage = $itemsPerPage = $pageTotalItems = null; |
||
65 | if ($paginated = $object instanceof PartialPaginatorInterface) { |
||
66 | if ($object instanceof PaginatorInterface) { |
||
67 | $paginated = 1. !== $lastPage = $object->getLastPage(); |
||
68 | } else { |
||
69 | $itemsPerPage = $object->getItemsPerPage(); |
||
70 | $pageTotalItems = (float) \count($object); |
||
71 | } |
||
72 | |||
73 | $currentPage = $object->getCurrentPage(); |
||
74 | } |
||
75 | |||
76 | $parsed = IriHelper::parseIri($context['request_uri'] ?? '/', $this->pageParameterName); |
||
77 | $appliedFilters = $parsed['parameters']; |
||
78 | unset($appliedFilters[$this->enabledParameterName]); |
||
79 | |||
80 | if (!$appliedFilters && !$paginated) { |
||
81 | return $data; |
||
82 | } |
||
83 | |||
84 | $metadata = isset($context['resource_class']) && null !== $this->resourceMetadataFactory ? $this->resourceMetadataFactory->create($context['resource_class']) : null; |
||
85 | $isPaginatedWithCursor = $paginated && null !== $metadata && null !== $cursorPaginationAttribute = $metadata->getCollectionOperationAttribute($context['collection_operation_name'] ?? $context['subresource_operation_name'], 'pagination_via_cursor', null, true); |
||
86 | |||
87 | $data['hydra:view'] = [ |
||
88 | '@id' => IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $paginated && !$isPaginatedWithCursor ? $currentPage : null), |
||
89 | '@type' => 'hydra:PartialCollectionView', |
||
90 | ]; |
||
91 | |||
92 | if ($isPaginatedWithCursor) { |
||
93 | $objects = iterator_to_array($object); |
||
94 | $firstObject = current($objects); |
||
95 | $lastObject = end($objects); |
||
96 | |||
97 | $data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters']); |
||
98 | |||
99 | if (false !== $lastObject && isset($cursorPaginationAttribute)) { |
||
100 | $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, 1, $lastObject))); |
||
101 | } |
||
102 | |||
103 | if (false !== $firstObject && isset($cursorPaginationAttribute)) { |
||
104 | $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, -1, $firstObject))); |
||
105 | } |
||
106 | } elseif ($paginated) { |
||
107 | if (null !== $lastPage) { |
||
108 | $data['hydra:view']['hydra:first'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, 1.); |
||
109 | $data['hydra:view']['hydra:last'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $lastPage); |
||
110 | } |
||
111 | |||
112 | if (1. !== $currentPage) { |
||
113 | $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage - 1.); |
||
114 | } |
||
115 | |||
116 | if (null !== $lastPage && $currentPage !== $lastPage || null === $lastPage && $pageTotalItems >= $itemsPerPage) { |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
117 | $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage + 1.); |
||
118 | } |
||
119 | } |
||
120 | |||
121 | return $data; |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * {@inheritdoc} |
||
126 | */ |
||
127 | public function supportsNormalization($data, $format = null) |
||
128 | { |
||
129 | return $this->collectionNormalizer->supportsNormalization($data, $format); |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * {@inheritdoc} |
||
134 | */ |
||
135 | public function hasCacheableSupportsMethod(): bool |
||
136 | { |
||
137 | return $this->collectionNormalizer instanceof CacheableSupportsMethodInterface && $this->collectionNormalizer->hasCacheableSupportsMethod(); |
||
138 | } |
||
139 | |||
140 | /** |
||
141 | * {@inheritdoc} |
||
142 | */ |
||
143 | public function setNormalizer(NormalizerInterface $normalizer) |
||
144 | { |
||
145 | if ($this->collectionNormalizer instanceof NormalizerAwareInterface) { |
||
146 | $this->collectionNormalizer->setNormalizer($normalizer); |
||
147 | } |
||
148 | } |
||
149 | |||
150 | private function cursorPaginationFields(array $fields, int $direction, $object) |
||
151 | { |
||
152 | $paginationFilters = []; |
||
153 | |||
154 | foreach ($fields as $field) { |
||
155 | $forwardRangeOperator = 'desc' === strtolower($field['direction']) ? 'lt' : 'gt'; |
||
156 | $backwardRangeOperator = 'gt' === $forwardRangeOperator ? 'lt' : 'gt'; |
||
157 | |||
158 | $operator = $direction > 0 ? $forwardRangeOperator : $backwardRangeOperator; |
||
159 | |||
160 | $paginationFilters[$field['field']] = [ |
||
161 | $operator => (string) $this->propertyAccessor->getValue($object, $field['field']), |
||
162 | ]; |
||
163 | } |
||
164 | |||
165 | return $paginationFilters; |
||
166 | } |
||
167 | } |
||
168 |