Completed
Push — master ( c80d49...d53149 )
by Antoine
18s queued 12s
created

cursorPaginationFields()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 16
rs 9.6111
c 0
b 0
f 0
cc 5
nc 9
nop 3
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\Normalizer\CacheableSupportsMethodInterface;
23
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
24
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
25
26
/**
27
 * Adds a view key to the result of a paginated Hydra collection.
28
 *
29
 * @author Kévin Dunglas <[email protected]>
30
 * @author Samuel ROZE <[email protected]>
31
 */
32
final class PartialCollectionViewNormalizer implements NormalizerInterface, NormalizerAwareInterface, CacheableSupportsMethodInterface
33
{
34
    private $collectionNormalizer;
35
    private $pageParameterName;
36
    private $enabledParameterName;
37
    private $resourceMetadataFactory;
38
    private $propertyAccessor;
39
40
    public function __construct(NormalizerInterface $collectionNormalizer, string $pageParameterName = 'page', string $enabledParameterName = 'pagination', ResourceMetadataFactoryInterface $resourceMetadataFactory = null, PropertyAccessorInterface $propertyAccessor = null)
41
    {
42
        $this->collectionNormalizer = $collectionNormalizer;
43
        $this->pageParameterName = $pageParameterName;
44
        $this->enabledParameterName = $enabledParameterName;
45
        $this->resourceMetadataFactory = $resourceMetadataFactory;
46
        $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function normalize($object, $format = null, array $context = [])
53
    {
54
        $data = $this->collectionNormalizer->normalize($object, $format, $context);
55
        if (isset($context['api_sub_level'])) {
56
            return $data;
57
        }
58
59
        $currentPage = $lastPage = $itemsPerPage = $pageTotalItems = null;
60
        if ($paginated = $object instanceof PartialPaginatorInterface) {
61
            if ($object instanceof PaginatorInterface) {
62
                $paginated = 1. !== $lastPage = $object->getLastPage();
63
            } else {
64
                $itemsPerPage = $object->getItemsPerPage();
65
                $pageTotalItems = (float) \count($object);
66
            }
67
68
            $currentPage = $object->getCurrentPage();
69
        }
70
71
        $parsed = IriHelper::parseIri($context['request_uri'] ?? '/', $this->pageParameterName);
72
        $appliedFilters = $parsed['parameters'];
73
        unset($appliedFilters[$this->enabledParameterName]);
74
75
        if (!$appliedFilters && !$paginated) {
76
            return $data;
77
        }
78
79
        $metadata = isset($context['resource_class']) && null !== $this->resourceMetadataFactory ? $this->resourceMetadataFactory->create($context['resource_class']) : null;
80
        $isPaginatedWithCursor = $paginated && null !== $metadata && null !== $cursorPaginationAttribute = $metadata->getCollectionOperationAttribute($context['collection_operation_name'], 'pagination_via_cursor', null, true);
81
82
        $data['hydra:view'] = [
83
            '@id' => IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $paginated && !$isPaginatedWithCursor ? $currentPage : null),
84
            '@type' => 'hydra:PartialCollectionView',
85
        ];
86
87
        if ($isPaginatedWithCursor) {
88
            $objects = iterator_to_array($object);
89
            $firstObject = current($objects);
90
            $lastObject = end($objects);
91
92
            $data['hydra:view']['@id'] = IriHelper::createIri($parsed['parts'], $parsed['parameters']);
93
94
            if (false !== $lastObject) {
95
                $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, 1, $lastObject)));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $cursorPaginationAttribute does not seem to be defined for all execution paths leading up to this point.
Loading history...
96
            }
97
98
            if (false !== $firstObject) {
99
                $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], array_merge($parsed['parameters'], $this->cursorPaginationFields($cursorPaginationAttribute, -1, $firstObject)));
100
            }
101
        } elseif ($paginated) {
102
            if (null !== $lastPage) {
103
                $data['hydra:view']['hydra:first'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, 1.);
104
                $data['hydra:view']['hydra:last'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $lastPage);
105
            }
106
107
            if (1. !== $currentPage) {
108
                $data['hydra:view']['hydra:previous'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage - 1.);
109
            }
110
111
            if (null !== $lastPage && $currentPage !== $lastPage || null === $lastPage && $pageTotalItems >= $itemsPerPage) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (null !== $lastPage && $...lItems >= $itemsPerPage, Probably Intended Meaning: null !== $lastPage && ($...Items >= $itemsPerPage)
Loading history...
112
                $data['hydra:view']['hydra:next'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage + 1.);
113
            }
114
        }
115
116
        return $data;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122
    public function supportsNormalization($data, $format = null)
123
    {
124
        return $this->collectionNormalizer->supportsNormalization($data, $format);
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130
    public function hasCacheableSupportsMethod(): bool
131
    {
132
        return $this->collectionNormalizer instanceof CacheableSupportsMethodInterface && $this->collectionNormalizer->hasCacheableSupportsMethod();
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function setNormalizer(NormalizerInterface $normalizer)
139
    {
140
        if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
141
            $this->collectionNormalizer->setNormalizer($normalizer);
142
        }
143
    }
144
145
    private function cursorPaginationFields(array $fields, int $direction, $object)
146
    {
147
        $paginationFilters = [];
148
149
        foreach ($fields as $field) {
150
            $forwardRangeOperator = 'desc' === strtolower($field['direction']) ? 'lt' : 'gt';
151
            $backwardRangeOperator = 'gt' === $forwardRangeOperator ? 'lt' : 'gt';
152
153
            $operator = $direction > 0 ? $forwardRangeOperator : $backwardRangeOperator;
154
155
            $paginationFilters[$field['field']] = [
156
                $operator => (string) $this->propertyAccessor->getValue($object, $field['field']),
157
            ];
158
        }
159
160
        return $paginationFilters;
161
    }
162
}
163