Completed
Push — master ( d7020d...a4700d )
by Daniel
06:50
created

supportsTransformation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Silverback\ApiComponentBundle\DataTransformer;
6
7
use ApiPlatform\Core\Api\OperationType;
8
use ApiPlatform\Core\Bridge\Doctrine\Orm\Paginator;
9
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
10
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
11
use ApiPlatform\Core\PathResolver\OperationPathResolverInterface;
12
use ApiPlatform\Core\Util\RequestParser;
13
use Silverback\ApiComponentBundle\Entity\Component\Collection\Collection;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
17
18
final class CollectionDataTransformer implements DataTransformerInterface
19
{
20
    private $requestStack;
21
    private $resourceMetadataFactory;
22
    private $operationPathResolver;
23
    private $dataProvider;
24
    private $itemNormalizer;
25
26
    public function __construct(
27
        RequestStack $requestStack,
28
        ResourceMetadataFactoryInterface $resourceMetadataFactory,
29
        OperationPathResolverInterface $operationPathResolver,
30
        ContextAwareCollectionDataProviderInterface $dataProvider,
31
        NormalizerInterface $itemNormalizer
32
    ) {
33
        $this->requestStack = $requestStack;
34
        $this->resourceMetadataFactory = $resourceMetadataFactory;
35
        $this->operationPathResolver = $operationPathResolver;
36
        $this->dataProvider = $dataProvider;
37
        $this->itemNormalizer = $itemNormalizer;
38
    }
39
40
    /**
41
     * @param Collection $object
42
     * @param array $context
43
     * @return object|void
44
     */
45
    public function transform($object, array $context = [])
46
    {
47
        $request = $this->requestStack->getCurrentRequest();
48
        if (!$request) {
49
            return;
50
        }
51
52
        $format = $request->getRequestFormat();
53
        $collectionRoutes = $this->addCollectionRoutes($object, $format);
0 ignored issues
show
Bug introduced by
It seems like $format can also be of type null; however, parameter $format of Silverback\ApiComponentB...::addCollectionRoutes() 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

53
        $collectionRoutes = $this->addCollectionRoutes($object, /** @scrutinizer ignore-type */ $format);
Loading history...
54
55
        $itemsPerPage = $object->getPerPage();
56
        $isPaginated = (bool) $itemsPerPage;
57
        $filters = $this->getFilters($object, $request);
58
59
        $dataProviderContext = null === $filters ? [] : ['filters' => $filters];
60
        if ($isPaginated) {
61
            $dataProviderContext['filters'] = $dataProviderContext['filters'] ?? [];
62
            $dataProviderContext['filters'] = array_merge($dataProviderContext['filters'], [
63
                'pagination' => true,
64
                'itemsPerPage' => $itemsPerPage,
65
                '_page' => 1
66
            ]);
67
            $request->attributes->set('_api_pagination', [
68
                'pagination' => 'true',
69
                'itemsPerPage' => $itemsPerPage
70
            ]);
71
        }
72
73
        /** @var Paginator $collection */
74
        $collection = $this->dataProvider->getCollection($object->getResource(), Request::METHOD_GET, $dataProviderContext);
75
76
        $forcedContext = [
77
            'resource_class' => $object->getResource(),
78
            'request_uri' => $collectionRoutes ? $collectionRoutes->first() : null,
0 ignored issues
show
introduced by
$collectionRoutes is of type Doctrine\Common\Collections\ArrayCollection, thus it always evaluated to true.
Loading history...
79
            'jsonld_has_context' => false,
80
            'api_sub_level' => null
81
        ];
82
        $mergedContext = array_merge($context, $forcedContext);
83
        $normalizedCollection = $this->itemNormalizer->normalize(
84
            $collection,
85
            $format,
86
            $mergedContext
87
        );
88
        if (\is_array($normalizedCollection)) {
89
            $object->setCollection($normalizedCollection);
90
        }
91
        return $object;
92
    }
93
94
    private function getFilters(Collection $object, Request $request): ?array
95
    {
96
        $filters = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $filters is dead and can be removed.
Loading history...
97
        $resetQueryString = false;
98
        // Set the default querystring for the RequestParser class if we have not passed one in the request
99
        if ($defaultQueryString = $object->getDefaultQueryString()) {
100
            $qs = $request->server->get('QUERY_STRING');
101
            if (!$qs) {
102
                $defaultQueryString = preg_replace('/{{(\s+)?NOW(\s+)?}}/i', (new \DateTime())->format('Y-m-d H:i:s'), $defaultQueryString);
103
                $resetQueryString = true;
104
                $request->server->set('QUERY_STRING', $defaultQueryString);
105
            }
106
        }
107
108
        if (null === $filters = $request->attributes->get('_api_filters')) {
109
            $queryString = RequestParser::getQueryString($request);
110
            $filters = $queryString ? RequestParser::parseRequestParams($queryString) : null;
111
        }
112
113
        if ($resetQueryString) {
114
            $request->server->set('QUERY_STRING', '');
115
        }
116
117
        return $filters;
118
    }
119
120
    private function addCollectionRoutes(Collection $object, string $format)
121
    {
122
        $resourceMetadata = $this->resourceMetadataFactory->create($object->getResource());
123
        $collectionOperations = $resourceMetadata->getCollectionOperations();
124
        if ($collectionOperations && ($shortName = $resourceMetadata->getShortName())) {
125
            $collectionOperations = array_change_key_case($collectionOperations, CASE_LOWER);
126
            $baseRoute = trim(trim($resourceMetadata->getAttribute('route_prefix', '')), '/');
127
            $methods = ['post', 'get'];
128
            foreach ($methods as $method) {
129
                if (array_key_exists($method, $collectionOperations)) {
130
                    $path = $baseRoute . $this->operationPathResolver->resolveOperationPath(
131
                        $shortName,
132
                        $collectionOperations[$method],
133
                        OperationType::COLLECTION,
134
                        $method
0 ignored issues
show
Unused Code introduced by
The call to ApiPlatform\Core\PathRes...:resolveOperationPath() has too many arguments starting with $method. ( Ignorable by Annotation )

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

134
                    $path = $baseRoute . $this->operationPathResolver->/** @scrutinizer ignore-call */ resolveOperationPath(

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...
135
                    );
136
                    $finalPath = preg_replace('/{_format}$/', $format, $path);
137
                    $object->addCollectionRoute(
138
                        $method,
139
                        $finalPath
140
                    );
141
                }
142
            }
143
        }
144
        return $object->getCollectionRoutes();
145
    }
146
147
    public function supportsTransformation($data, array $context = []): bool
148
    {
149
        return $data instanceof Collection;
150
    }
151
}
152