Passed
Push — master ( ce7fff...9920f1 )
by Alan
04:04
created

Serializer/PartialCollectionViewNormalizer.php (1 issue)

Labels
Severity
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) {
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
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 Symfony\Component\Serial...er\SerializerNormalizer or ApiPlatform\Core\GraphQl...ception\ErrorNormalizer or ApiPlatform\Core\Tests\F...DocumentationNormalizer or Symfony\Component\Serial...rmalizer\TestNormalizer or Symfony\Component\Serial...sonSerializerNormalizer or ApiPlatform\Core\GraphQl...tionExceptionNormalizer or Symfony\Component\Serial...wareNormalizerInterface or Symfony\Component\Serial...ectSerializerNormalizer 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);
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