PartialCollectionViewNormalizer   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 133
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 64
dl 0
loc 133
rs 9.52
c 0
b 0
f 0
wmc 36

6 Methods

Rating   Name   Duplication   Size   Complexity  
A setNormalizer() 0 4 2
A cursorPaginationFields() 0 16 5
A supportsNormalization() 0 3 1
A hasCacheableSupportsMethod() 0 3 2
A __construct() 0 7 1
F normalize() 0 69 25
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
Consider adding parentheses for clarity. Current Interpretation: (null !== $lastPage && $...lItems >= $itemsPerPage, Probably Intended Meaning: null !== $lastPage && ($...Items >= $itemsPerPage)
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();
0 ignored issues
show
Bug introduced by
The method hasCacheableSupportsMethod() does not exist on Symfony\Component\Serial...zer\NormalizerInterface. It seems like you code against a sub-type of said class. However, the method does not exist in ApiPlatform\Core\GraphQl...HttpExceptionNormalizer or ApiPlatform\Core\GraphQl...ception\ErrorNormalizer or ApiPlatform\Core\Tests\F...DocumentationNormalizer or ApiPlatform\Core\GraphQl...tionExceptionNormalizer or Symfony\Component\Serial...wareNormalizerInterface or ApiPlatform\Core\GraphQl...timeExceptionNormalizer or Symfony\Component\Serializer\Serializer. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

137
        return $this->collectionNormalizer instanceof CacheableSupportsMethodInterface && $this->collectionNormalizer->/** @scrutinizer ignore-call */ hasCacheableSupportsMethod();
Loading history...
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function setNormalizer(NormalizerInterface $normalizer)
144
    {
145
        if ($this->collectionNormalizer instanceof NormalizerAwareInterface) {
146
            $this->collectionNormalizer->setNormalizer($normalizer);
0 ignored issues
show
Bug introduced by
The method setNormalizer() does not exist on Symfony\Component\Serial...zer\NormalizerInterface. It seems like you code against a sub-type of Symfony\Component\Serial...zer\NormalizerInterface such as ApiPlatform\Core\Hydra\S...ectionFiltersNormalizer or ApiPlatform\Core\Seriali...actCollectionNormalizer or ApiPlatform\Core\Hydra\S...ollectionViewNormalizer or ApiPlatform\Core\Hydra\S...er\CollectionNormalizer. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

146
            $this->collectionNormalizer->/** @scrutinizer ignore-call */ 
147
                                         setNormalizer($normalizer);
Loading history...
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