Test Failed
Pull Request — main (#140)
by Daniel
04:22
created

CollectionOutputDataTransformer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 8
nc 1
nop 8
dl 0
loc 18
ccs 0
cts 8
cp 0
crap 2
rs 10
c 1
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\CollectionOperationInterface;
20
use ApiPlatform\Metadata\HttpOperation;
21
use ApiPlatform\Metadata\Operation;
22
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
23
use ApiPlatform\Serializer\SerializerContextBuilderInterface;
24
use ApiPlatform\State\ProviderInterface;
25
use ApiPlatform\State\UriVariablesResolverTrait;
26
use ApiPlatform\Util\AttributesExtractor;
27
use ApiPlatform\Util\RequestParser;
28
use Silverback\ApiComponentsBundle\Entity\Component\Collection;
29
use Silverback\ApiComponentsBundle\Exception\OutOfBoundsException;
30
use Silverback\ApiComponentsBundle\Serializer\SerializeFormatResolver;
31
use Silverback\ApiComponentsBundle\Utility\ApiResourceRouteFinder;
32
use Symfony\Component\HttpFoundation\RequestStack;
33
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
34
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
35
36
/**
37
 * @author Daniel West <[email protected]>
38
 */
39
class CollectionOutputDataTransformer implements DataTransformerInterface
40
{
41
    use UriVariablesResolverTrait;
42
43
    private ApiResourceRouteFinder $resourceRouteFinder;
44
    private ProviderInterface $provider;
45
    private RequestStack $requestStack;
46
    private SerializerContextBuilderInterface $serializerContextBuilder;
47
    private NormalizerInterface $itemNormalizer;
48
    private SerializeFormatResolver $serializeFormatResolver;
49
    private string $itemsPerPageParameterName;
50
    private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory;
51
52
    public function __construct(
53
        ApiResourceRouteFinder $resourceRouteFinder,
54
        ProviderInterface $provider,
55
        RequestStack $requestStack,
56
        SerializerContextBuilderInterface $serializerContextBuilder,
57
        NormalizerInterface $itemNormalizer,
58
        SerializeFormatResolver $serializeFormatResolver,
59
        ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
60
        string $itemsPerPageParameterName
61
    ) {
62
        $this->resourceRouteFinder = $resourceRouteFinder;
63
        $this->provider = $provider;
64
        $this->requestStack = $requestStack;
65
        $this->serializerContextBuilder = $serializerContextBuilder;
66
        $this->itemNormalizer = $itemNormalizer;
67
        $this->serializeFormatResolver = $serializeFormatResolver;
68
        $this->itemsPerPageParameterName = $itemsPerPageParameterName;
69
        $this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
70
    }
71
72
    public function supportsTransformation($data, string $to, array $context = []): bool
73
    {
74
        return $data instanceof Collection && Collection::class === $to;
75
    }
76
77
    /**
78
     * @param object|Collection $object
79
     */
80
    public function transform($object, string $to, array $context = []): Collection
81
    {
82
        $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

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

123
            $collectionData = $this->provider->provide($resourceClass, $uriVariables, /** @scrutinizer ignore-type */ $getCollectionOperation->getName(), $collectionContext);
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

123
            $collectionData = $this->provider->provide(/** @scrutinizer ignore-type */ $resourceClass, $uriVariables, $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

123
            /** @scrutinizer ignore-call */ 
124
            $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...
124
        } catch (InvalidIdentifierException $e) {
125
            throw new NotFoundHttpException('Invalid identifier value or configuration.', $e);
126
        }
127
128
        // Normalize the collection into an array
129
        // Pagination disabled
130
        if (\is_array($collectionData)) {
131
            $collection = $collectionData;
132
        } else {
133
            if (!$collectionData instanceof \Traversable) {
134
                throw new OutOfBoundsException('$collectionData should be Traversable');
135
            }
136
            $collection = iterator_count($collectionData) ? $collectionData : [];
137
        }
138
        $format = $this->serializeFormatResolver->getFormatFromRequest($request);
139
        $normalizedCollection = $this->itemNormalizer->normalize($collection, $format, $normalizationContext);
140
141
        // Update the original collection resource
142
        $object->setCollection($normalizedCollection);
143
144
        return $object;
145
    }
146
147
    private function findGetCollectionOperation(string $resourceClass): ?Operation
148
    {
149
        $metadata = $this->resourceMetadataCollectionFactory->create($resourceClass);
150
        $it = $metadata->getIterator();
151
        /** @var ApiResource $apiResource */
152
        foreach ($it as $apiResource) {
153
            $operations = $apiResource->getOperations();
154
            if ($operations) {
155
                /** @var Operation $operation */
156
                foreach ($operations as $operation) {
157
                    if ($operation instanceof CollectionOperationInterface && HttpOperation::METHOD_GET === $operation->getMethod()) {
0 ignored issues
show
Bug introduced by
The method getMethod() does not exist on ApiPlatform\Metadata\CollectionOperationInterface. It seems like you code against a sub-type of ApiPlatform\Metadata\CollectionOperationInterface such as ApiPlatform\Metadata\GetCollection. ( Ignorable by Annotation )

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

157
                    if ($operation instanceof CollectionOperationInterface && HttpOperation::METHOD_GET === $operation->/** @scrutinizer ignore-call */ getMethod()) {
Loading history...
158
                        return $operation;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $operation returns the type ApiPlatform\Metadata\CollectionOperationInterface which is incompatible with the type-hinted return ApiPlatform\Metadata\Operation|null.
Loading history...
159
                    }
160
                }
161
            }
162
        }
163
164
        return null;
165
    }
166
}
167