Test Failed
Pull Request — main (#139)
by Daniel
16:46
created

CollectionOutputDataTransformer   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 65
dl 0
loc 126
ccs 0
cts 53
cp 0
rs 10
c 1
b 0
f 0
wmc 21

4 Methods

Rating   Name   Duplication   Size   Complexity  
A findGetCollectionOperation() 0 18 6
A __construct() 0 18 1
A supportsTransformation() 0 3 2
C transform() 0 65 12
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components Bundle Project
5
 *
6
 * (c) Daniel West <[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 Silverback\ApiComponentsBundle\DataTransformer;
15
16
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
17
use ApiPlatform\Exception\InvalidIdentifierException;
18
use ApiPlatform\Metadata\ApiResource;
19
use ApiPlatform\Metadata\Operation;
20
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
21
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
22
use ApiPlatform\State\ProviderInterface;
23
use ApiPlatform\State\UriVariablesResolverTrait;
24
use ApiPlatform\Util\AttributesExtractor;
25
use ApiPlatform\Util\RequestParser;
26
use Silverback\ApiComponentsBundle\Entity\Component\Collection;
27
use Silverback\ApiComponentsBundle\Exception\OutOfBoundsException;
28
use Silverback\ApiComponentsBundle\Serializer\SerializeFormatResolver;
29
use Silverback\ApiComponentsBundle\Utility\ApiResourceRouteFinder;
30
use Symfony\Component\HttpFoundation\RequestStack;
31
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
32
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
33
34
/**
35
 * @author Daniel West <[email protected]>
36
 */
37
class CollectionOutputDataTransformer implements DataTransformerInterface
38
{
39
    use UriVariablesResolverTrait;
40
41
    private ApiResourceRouteFinder $resourceRouteFinder;
42
    private ProviderInterface $provider;
43
    private RequestStack $requestStack;
44
    private SerializerContextBuilderInterface $serializerContextBuilder;
45
    private NormalizerInterface $itemNormalizer;
46
    private SerializeFormatResolver $serializeFormatResolver;
47
    private string $itemsPerPageParameterName;
48
    private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory;
49
50
    public function __construct(
51
        ApiResourceRouteFinder $resourceRouteFinder,
52
        ProviderInterface $provider,
53
        RequestStack $requestStack,
54
        SerializerContextBuilderInterface $serializerContextBuilder,
55
        NormalizerInterface $itemNormalizer,
56
        SerializeFormatResolver $serializeFormatResolver,
57
        ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
58
        string $itemsPerPageParameterName
59
    ) {
60
        $this->resourceRouteFinder = $resourceRouteFinder;
61
        $this->provider = $provider;
62
        $this->requestStack = $requestStack;
63
        $this->serializerContextBuilder = $serializerContextBuilder;
64
        $this->itemNormalizer = $itemNormalizer;
65
        $this->serializeFormatResolver = $serializeFormatResolver;
66
        $this->itemsPerPageParameterName = $itemsPerPageParameterName;
67
        $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
68
    }
69
70
    public function supportsTransformation($data, string $to, array $context = []): bool
71
    {
72
        return $data instanceof Collection && Collection::class === $to;
73
    }
74
75
    /**
76
     * @param object|Collection $object
77
     */
78
    public function transform($object, string $to, array $context = []): Collection
79
    {
80
        $parameters = $this->resourceRouteFinder->findByIri($object->getResourceIri());
0 ignored issues
show
Bug introduced by
It seems like $object->getResourceIri() can also be of type null; however, parameter $iri of Silverback\ApiComponents...outeFinder::findByIri() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

80
        $parameters = $this->resourceRouteFinder->findByIri(/** @scrutinizer ignore-type */ $object->getResourceIri());
Loading history...
81
        $attributes = AttributesExtractor::extractAttributes($parameters);
82
        $request = $this->requestStack->getMainRequest();
83
        if (!$request) {
84
            return $object;
85
        }
86
        // Fetch the collection with computed context
87
        $resourceClass = $attributes['resource_class'];
88
89
        $getCollectionOperation = $this->findGetCollectionOperation($resourceClass);
90
        if (!$getCollectionOperation) {
91
            return $object;
92
        }
93
94
        // Build context
95
        $collectionContext = ['operation' => $getCollectionOperation];
96
97
        // Build filters
98
        $filters = [];
99
        if (($perPage = $object->getPerPage()) !== null) {
100
            $filters[$this->itemsPerPageParameterName] = $perPage;
101
        }
102
        if (($defaultQueryParams = $object->getDefaultQueryParameters()) !== null) {
103
            $filters += $defaultQueryParams;
104
        }
105
        if (null === $requestFilters = $request->attributes->get('_api_filters')) {
106
            $queryString = RequestParser::getQueryString($request);
107
            $requestFilters = $queryString ? RequestParser::parseRequestParams($queryString) : null;
108
        }
109
        if ($requestFilters) {
110
            // not += because we want to overwrite with an empty string if provided in querystring.
111
            // e.g. a default search value could be overridden by no search value
112
            $filters = array_merge($filters, $requestFilters);
113
        }
114
        $collectionContext['filters'] = $filters;
115
116
        // Compose context for provider
117
        $collectionContext += $normalizationContext = $this->serializerContextBuilder->createFromRequest($request, true, $attributes);
118
119
        try {
120
            $uriVariables = $this->getOperationUriVariables($getCollectionOperation, $parameters, $resourceClass);
121
            $collectionData = $this->provider->provide($resourceClass, $uriVariables, $getCollectionOperation->getName(), $collectionContext);
0 ignored issues
show
Bug introduced by
$getCollectionOperation->getName() of type null|string is incompatible with the type array expected by parameter $context of ApiPlatform\State\ProviderInterface::provide(). ( Ignorable by Annotation )

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

121
            $collectionData = $this->provider->provide($resourceClass, $uriVariables, /** @scrutinizer ignore-type */ $getCollectionOperation->getName(), $collectionContext);
Loading history...
Unused Code introduced by
The call to ApiPlatform\State\ProviderInterface::provide() has too many arguments starting with $collectionContext. ( Ignorable by Annotation )

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

121
            /** @scrutinizer ignore-call */ 
122
            $collectionData = $this->provider->provide($resourceClass, $uriVariables, $getCollectionOperation->getName(), $collectionContext);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by
$resourceClass of type string is incompatible with the type ApiPlatform\Metadata\Operation expected by parameter $operation of ApiPlatform\State\ProviderInterface::provide(). ( Ignorable by Annotation )

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

121
            $collectionData = $this->provider->provide(/** @scrutinizer ignore-type */ $resourceClass, $uriVariables, $getCollectionOperation->getName(), $collectionContext);
Loading history...
122
        } catch (InvalidIdentifierException $e) {
123
            throw new NotFoundHttpException('Invalid identifier value or configuration.', $e);
124
        }
125
126
        // Normalize the collection into an array
127
        // Pagination disabled
128
        if (\is_array($collectionData)) {
129
            $collection = $collectionData;
130
        } else {
131
            if (!$collectionData instanceof \Traversable) {
132
                throw new OutOfBoundsException('$collectionData should be Traversable');
133
            }
134
            $collection = iterator_count($collectionData) ? $collectionData : [];
135
        }
136
        $format = $this->serializeFormatResolver->getFormatFromRequest($request);
137
        $normalizedCollection = $this->itemNormalizer->normalize($collection, $format, $normalizationContext);
138
139
        // Update the original collection resource
140
        $object->setCollection($normalizedCollection);
141
142
        return $object;
143
    }
144
145
    private function findGetCollectionOperation(string $resourceClass): ?Operation
146
    {
147
        $metadata = $this->resourceMetadataCollectionFactory->create($resourceClass);
148
        $it = $metadata->getIterator();
149
        /** @var ApiResource $apiResource */
150
        foreach ($it as $apiResource) {
151
            $operations = $apiResource->getOperations();
152
            if ($operations) {
153
                /** @var Operation $operation */
154
                foreach ($operations as $operation) {
155
                    if ($operation->isCollection() && Operation::METHOD_GET === $operation->getMethod()) {
0 ignored issues
show
Bug introduced by
The constant ApiPlatform\Metadata\Operation::METHOD_GET was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
156
                        return $operation;
157
                    }
158
                }
159
            }
160
        }
161
162
        return null;
163
    }
164
}
165