Passed
Push — master ( 413ea4...a7ed20 )
by Daniel
22:03 queued 16:10
created

CollectionOutputDataTransformer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 9
dl 0
loc 11
ccs 0
cts 10
cp 0
crap 2
rs 9.9666
c 0
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\Api\IriConverterInterface;
17
use ApiPlatform\Core\Api\OperationType;
18
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
19
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
20
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
21
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
22
use ApiPlatform\Core\Util\RequestParser;
23
use Silverback\ApiComponentsBundle\Entity\Component\Collection;
24
use Silverback\ApiComponentsBundle\Serializer\SerializeFormatResolver;
25
use Symfony\Component\HttpFoundation\Request;
26
use Symfony\Component\HttpFoundation\RequestStack;
27
use Symfony\Component\HttpFoundation\UrlHelper;
28
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
29
use Traversable;
30
31
/**
32
 * @author Daniel West <[email protected]>
33
 */
34
class CollectionOutputDataTransformer implements DataTransformerInterface
35
{
36
    private array $transformed = [];
37
    private RequestStack $requestStack;
38
    private ResourceMetadataFactoryInterface $resourceMetadataFactory;
39
    private OperationPathResolverInterface $operationPathResolver;
40
    private ContextAwareCollectionDataProviderInterface $dataProvider;
41
    private IriConverterInterface $iriConverter;
42
    private NormalizerInterface $itemNormalizer;
43
    private SerializeFormatResolver $requestFormatResolver;
44
    private UrlHelper $urlHelper;
45
    private string $itemsPerPageParameterName;
46
47
    public function __construct(RequestStack $requestStack, ResourceMetadataFactoryInterface $resourceMetadataFactory, OperationPathResolverInterface $operationPathResolver, ContextAwareCollectionDataProviderInterface $dataProvider, IriConverterInterface $iriConverter, NormalizerInterface $itemNormalizer, SerializeFormatResolver $requestFormatResolver, UrlHelper $urlHelper, string $itemsPerPageParameterName = 'perPage')
48
    {
49
        $this->requestStack = $requestStack;
50
        $this->resourceMetadataFactory = $resourceMetadataFactory;
51
        $this->operationPathResolver = $operationPathResolver;
52
        $this->dataProvider = $dataProvider;
53
        $this->iriConverter = $iriConverter;
54
        $this->itemNormalizer = $itemNormalizer;
55
        $this->requestFormatResolver = $requestFormatResolver;
56
        $this->urlHelper = $urlHelper;
57
        $this->itemsPerPageParameterName = $itemsPerPageParameterName;
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     *
63
     * @param Collection $collection
64
     */
65
    public function transform($collection, string $to, array $context = [])
66
    {
67
        $this->transformed[] = $collection->getId();
68
69
        $request = $this->requestStack->getMasterRequest();
70
        if (!$request) {
71
            return $collection;
72
        }
73
        $format = $this->requestFormatResolver->getFormatFromRequest($request);
74
75
        $this->addEndpoints($collection, $format);
76
        $this->addCollection($collection, $format, $request);
77
78
        return $collection;
79
    }
80
81
    private function addCollection(Collection $collection, string $format, Request $request): void
82
    {
83
        $filters = $this->getFilters($collection, $request);
84
        $dataProviderContext = $this->getDataProviderContext($collection, $request, $filters);
85
        $resourceClass = $collection->getResourceClass();
86
87
        $endpoints = $collection->getEndpoints();
88
        $forcedContext = [
89
            'resource_class' => $resourceClass,
90
            'request_uri' => $endpoints ? $endpoints->get('get') : null,
91
            'jsonld_has_context' => false,
92
            'api_sub_level' => null,
93
            'subresource_operation_name' => 'get',
94
        ];
95
        $normalizerContext = array_merge([], $forcedContext);
96
97
        /**
98
         * !!! WE NEED TO CHECK WHETHER THIS WILL WORK WHEN PAGINATION IS DISABLED...
99
         * DOES PAGINATION OBJECT GET RETURNED OR ANOTHER ITERABLE OR IS IT JUST AN ARRAY??
100
         * THIS WILL NEED TO BE HANDLED !!!
101
         *
102
         * @var Traversable
103
         */
104
        $resourceCollection = $this->dataProvider->getCollection($resourceClass, Request::METHOD_GET, $dataProviderContext);
105
106
        $normalizedCollection = $this->itemNormalizer->normalize(
107
            $resourceCollection,
108
            $format,
109
            $normalizerContext
110
        );
111
        // IS THIS CHECK NEEDED??
112
        if (\is_array($normalizedCollection)) {
113
            $collection->setCollection($normalizedCollection);
114
        }
115
116
        $resources = array_map(function ($object) {
117
            return $this->iriConverter->getIriFromItem($object);
118
        }, (array) $resourceCollection->getIterator());
119
120
        $request->attributes->set('_resources', $request->attributes->get('_resources', []) + $resources);
121
    }
122
123
    private function getDataProviderContext(Collection $collection, Request $request, array $filters): array
124
    {
125
        $itemsPerPage = $collection->getPerPage();
126
        $isPaginated = (bool) $itemsPerPage;
127
        $dataProviderContext = ['filters' => $filters];
128
        if ($isPaginated) {
129
            $dataProviderContext['filters'] = $dataProviderContext['filters'] ?? [];
130
            $dataProviderContext['filters'] = array_merge($dataProviderContext['filters'], [
131
                'pagination' => true,
132
                $this->itemsPerPageParameterName => $itemsPerPage,
133
                '_page' => 1,
134
            ]);
135
            $request->attributes->set('_api_pagination', [
136
                'pagination' => 'true',
137
                $this->itemsPerPageParameterName => $itemsPerPage,
138
            ]);
139
        }
140
141
        return $dataProviderContext;
142
    }
143
144
    private function getFilters(Collection $collection, Request $request): array
145
    {
146
        if (null === $filters = $request->attributes->get('_api_filters')) {
147
            $defaultQueryString = $collection->getDefaultQueryParameters();
148
            $setDefaultQuery = $defaultQueryString && !$request->server->get('QUERY_STRING');
149
            if ($setDefaultQuery) {
150
                $request->server->set('QUERY_STRING', http_build_query($defaultQueryString));
151
            }
152
            $queryString = RequestParser::getQueryString($request);
153
            $filters = $queryString ? RequestParser::parseRequestParams($queryString) : [];
154
            if ($setDefaultQuery) {
155
                $request->server->set('QUERY_STRING', '');
156
            }
157
        }
158
159
        return $filters;
160
    }
161
162
    private function addEndpoints(Collection $collection, string $format): void
163
    {
164
        $resourceMetadata = $this->resourceMetadataFactory->create($collection->getResourceClass());
165
        $collectionOperations = array_change_key_case($resourceMetadata->getCollectionOperations() ?? [], CASE_LOWER);
166
        if (!empty($collectionOperations) && ($shortName = $resourceMetadata->getShortName())) {
167
            $baseRoute = '/' . trim($resourceMetadata->getAttribute('route_prefix', ''), ' /');
168
            $methods = array_map(static function ($str) { return strtolower($str); }, [Request::METHOD_GET, Request::METHOD_POST]);
169
            foreach ($methods as $method) {
170
                if (!\array_key_exists($method, $collectionOperations)) {
171
                    continue;
172
                }
173
                $path = $this->operationPathResolver->resolveOperationPath(
174
                        $shortName,
175
                        $collectionOperations[$method],
176
                        OperationType::COLLECTION
177
                    );
178
                $finalPath = preg_replace('/{_format}$/', $format, $path);
179
                $absoluteUrl = $this->urlHelper->getAbsoluteUrl($baseRoute . $finalPath);
180
                $collection->addEndpoint($method, $absoluteUrl);
181
            }
182
        }
183
    }
184
185
    /**
186
     * {@inheritdoc}
187
     */
188
    public function supportsTransformation($data, string $to, array $context = []): bool
189
    {
190
        return $data instanceof Collection &&
191
            Collection::class === $to &&
192
            !\in_array($data->getId(), $this->transformed, true);
193
    }
194
}
195