vendor/api-platform/core/src/Hydra/Serializer/PartialCollectionViewNormalizer.php line 42

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the API Platform project.
  4. *
  5. * (c) Kévin Dunglas <dunglas@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. declare(strict_types=1);
  11. namespace ApiPlatform\Hydra\Serializer;
  12. use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
  13. use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
  14. use ApiPlatform\State\Pagination\PaginatorInterface;
  15. use ApiPlatform\State\Pagination\PartialPaginatorInterface;
  16. use ApiPlatform\Util\IriHelper;
  17. use Symfony\Component\PropertyAccess\PropertyAccess;
  18. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  19. use Symfony\Component\Serializer\Exception\UnexpectedValueException;
  20. use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
  21. use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
  22. use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
  23. /**
  24. * Adds a view key to the result of a paginated Hydra collection.
  25. *
  26. * @author Kévin Dunglas <dunglas@gmail.com>
  27. * @author Samuel ROZE <samuel.roze@gmail.com>
  28. */
  29. final class PartialCollectionViewNormalizer implements NormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface
  30. {
  31. private $collectionNormalizer;
  32. private $pageParameterName;
  33. private $enabledParameterName;
  34. private $resourceMetadataFactory;
  35. private $propertyAccessor;
  36. public function __construct(NormalizerInterface $collectionNormalizer, string $pageParameterName = 'page', string $enabledParameterName = 'pagination', $resourceMetadataFactory = null, PropertyAccessorInterface $propertyAccessor = null)
  37. {
  38. $this->collectionNormalizer = $collectionNormalizer;
  39. $this->pageParameterName = $pageParameterName;
  40. $this->enabledParameterName = $enabledParameterName;
  41. $this->resourceMetadataFactory = $resourceMetadataFactory;
  42. if (!$resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface) {
  43. trigger_deprecation('api-platform/core', '2.7', sprintf('Use "%s" instead of "%s".', ResourceMetadataCollectionFactoryInterface::class, ResourceMetadataFactoryInterface::class));
  44. }
  45. $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
  46. }
  47. /**
  48. * @param mixed|null $format
  49. *
  50. * @return array|string|int|float|bool|\ArrayObject|null
  51. */
  52. public function normalize($object, $format = null, array $context = [])
  53. {
  54. $data = $this->collectionNormalizer->normalize($object, $format, $context);
  55. if (!\is_array($data)) {
  56. throw new UnexpectedValueException('Expected data to be an array');
  57. }
  58. if (isset($context['api_sub_level'])) {
  59. return $data;
  60. }
  61. $currentPage = $lastPage = $itemsPerPage = $pageTotalItems = null;
  62. if ($paginated = ($object instanceof PartialPaginatorInterface)) {
  63. if ($object instanceof PaginatorInterface) {
  64. $paginated = 1. !== $lastPage = $object->getLastPage();
  65. } else {
  66. $itemsPerPage = $object->getItemsPerPage();
  67. $pageTotalItems = (float) \count($object);
  68. }
  69. $currentPage = $object->getCurrentPage();
  70. }
  71. $parsed = IriHelper::parseIri($context['request_uri'] ?? '/', $this->pageParameterName);
  72. $appliedFilters = $parsed['parameters'];
  73. unset($appliedFilters[$this->enabledParameterName]);
  74. if (!$appliedFilters && !$paginated) {
  75. return $data;
  76. }
  77. $isPaginatedWithCursor = false;
  78. $cursorPaginationAttribute = null;
  79. if ($this->resourceMetadataFactory instanceof ResourceMetadataFactoryInterface && isset($context['resource_class']) && $paginated) {
  80. $metadata = $this->resourceMetadataFactory->create($context['resource_class']);
  81. $isPaginatedWithCursor = null !== $cursorPaginationAttribute = $metadata->getCollectionOperationAttribute($context['collection_operation_name'] ?? $context['subresource_operation_name'], 'pagination_via_cursor', null, true);
  82. } elseif ($this->resourceMetadataFactory instanceof ResourceMetadataCollectionFactoryInterface && isset($context['resource_class']) && $paginated) {
  83. $operation = $this->resourceMetadataFactory->create($context['resource_class'])->getOperation($context['operation_name'] ?? null);
  84. $isPaginatedWithCursor = [] !== $cursorPaginationAttribute = ($operation->getPaginationViaCursor() ?? []);
  85. }
  86. $data['hydra:view'] = ['@id' => null, '@type' => 'hydra:PartialCollectionView'];
  87. if ($isPaginatedWithCursor) {
  88. return $this->populateDataWithCursorBasedPagination($data, $parsed, $object, $cursorPaginationAttribute);
  89. }
  90. $data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $paginated ? $currentPage : null);
  91. if ($paginated) {
  92. return $this->populateDataWithPagination($data, $parsed, $currentPage, $lastPage, $itemsPerPage, $pageTotalItems);
  93. }
  94. return $data;
  95. }
  96. public function supportsNormalization($data, $format = null, array $context = []): bool
  97. {
  98. return $this->collectionNormalizer->supportsNormalization($data, $format, $context);
  99. }
  100. public function hasCacheableSupportsMethod(): bool
  101. {
  102. return $this->collectionNormalizer instanceof CacheableSupportsMethodInterface && $this->collectionNormalizer->hasCacheableSupportsMethod();
  103. }
  104. public function setNormalizer(NormalizerInterface $normalizer)
  105. {
  106. if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
  107. $this->collectionNormalizer->setNormalizer($normalizer);
  108. }
  109. }
  110. private function cursorPaginationFields(array $fields, int $direction, $object)
  111. {
  112. $paginationFilters = [];
  113. foreach ($fields as $field) {
  114. $forwardRangeOperator = 'desc' === strtolower($field['direction']) ? 'lt' : 'gt';
  115. $backwardRangeOperator = 'gt' === $forwardRangeOperator ? 'lt' : 'gt';
  116. $operator = $direction > 0 ? $forwardRangeOperator : $backwardRangeOperator;
  117. $paginationFilters[$field['field']] = [
  118. $operator => (string) $this->propertyAccessor->getValue($object, $field['field']),
  119. ];
  120. }
  121. return $paginationFilters;
  122. }
  123. private function populateDataWithCursorBasedPagination(array $data, array $parsed, \Traversable $object, $cursorPaginationAttribute): array
  124. {
  125. $objects = iterator_to_array($object);
  126. $firstObject = current($objects);
  127. $lastObject = end($objects);
  128. $data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters']);
  129. if (false !== $lastObject && \is_array($cursorPaginationAttribute)) {
  130. $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, 1, $lastObject)));
  131. }
  132. if (false !== $firstObject && \is_array($cursorPaginationAttribute)) {
  133. $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, -1, $firstObject)));
  134. }
  135. return $data;
  136. }
  137. private function populateDataWithPagination(array $data, array $parsed, ?float $currentPage, ?float $lastPage, ?float $itemsPerPage, ?float $pageTotalItems): array
  138. {
  139. if (null !== $lastPage) {
  140. $data['hydra:view']['hydra:first'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, 1.);
  141. $data['hydra:view']['hydra:last'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $lastPage);
  142. }
  143. if (1. !== $currentPage) {
  144. $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage - 1.);
  145. }
  146. if ((null !== $lastPage && $currentPage < $lastPage) || (null === $lastPage && $pageTotalItems >= $itemsPerPage)) {
  147. $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage + 1.);
  148. }
  149. return $data;
  150. }
  151. }
  152. class_alias(PartialCollectionViewNormalizer::class, \ApiPlatform\Core\Hydra\Serializer\PartialCollectionViewNormalizer::class);