Completed
Push — v2 ( 02db1b...ed3360 )
by Daniel
04:31
created

FileRequestHandler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 5
dl 0
loc 12
ccs 0
cts 6
cp 0
crap 2
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Component 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\ApiComponentBundle\File;
15
16
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
17
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
18
use ApiPlatform\Core\Exception\ResourceClassNotSupportedException;
19
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
20
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
21
use Silverback\ApiComponentBundle\Entity\Utility\FileInterface;
22
use Symfony\Component\HttpFoundation\BinaryFileResponse;
23
use Symfony\Component\HttpFoundation\Request;
24
use Symfony\Component\HttpFoundation\Response;
25
use Symfony\Component\PropertyAccess\PropertyAccess;
26
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
27
use Symfony\Component\Routing\RequestContext;
28
use Symfony\Component\Serializer\SerializerInterface;
29
30
/**
31
 * @author Daniel West <[email protected]>
32
 */
33
class FileRequestHandler
34
{
35
    private UrlMatcherInterface $urlMatcher;
36
    private ItemDataProviderInterface $itemDataProvider;
37
    private FileUploader $fileUploader;
38
    private ResourceMetadataFactoryInterface $resourceMetadataFactory;
39
    private SerializerInterface $serializer;
40
41
    public function __construct(
42
        UrlMatcherInterface $urlMatcher,
43
        ItemDataProviderInterface $itemDataProvider,
44
        FileUploader $fileUploader,
45
        ResourceMetadataFactoryInterface $resourceMetadataFactory,
46
        SerializerInterface $serializer)
47
    {
48
        $this->urlMatcher = $urlMatcher;
49
        $this->itemDataProvider = $itemDataProvider;
50
        $this->fileUploader = $fileUploader;
51
        $this->resourceMetadataFactory = $resourceMetadataFactory;
52
        $this->serializer = $serializer;
53
    }
54
55
    public function handle(Request $request, ?string $_format, string $field, string $id): Response
56
    {
57
        try {
58
            $routeParams = $this->getRouteParamsByIri($request, $id);
59
            $entity = $this->getEntityByRouteParams($routeParams);
60
61
//            if (!$this->restrictedResourceVoter->vote($entity)) {
62
//                throw new AccessDeniedException('You are not permitted to download this file');
63
//            }
64
65
            if (Request::METHOD_GET === ($requestMethod = $request->getMethod())) {
66
                return $this->getFileResponse($entity, $field);
67
            }
68
69
            $resourceMetadata = $this->resourceMetadataFactory->create($routeParams['_api_resource_class']);
70
            $this->handleFileUpload($request, $entity, $field, $requestMethod);
71
72
            return $this->getSerializedResourceResponse($entity, $_format, $requestMethod, $resourceMetadata);
0 ignored issues
show
Bug introduced by
It seems like $_format can also be of type null; however, parameter $_format of Silverback\ApiComponentB...lizedResourceResponse() 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

72
            return $this->getSerializedResourceResponse($entity, /** @scrutinizer ignore-type */ $_format, $requestMethod, $resourceMetadata);
Loading history...
73
        } catch (\InvalidArgumentException $exception) {
74
            return new Response($exception->getMessage(), Response::HTTP_BAD_REQUEST);
75
        } catch (\RuntimeException $exception) {
76
            return new Response($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
77
        } catch (ResourceClassNotFoundException $exception) {
78
            return new Response($exception->getMessage(), Response::HTTP_BAD_REQUEST);
79
        }
80
    }
81
82
    private function getSerializedResourceResponse(FileInterface $entity, string $_format, string $requestMethod, ResourceMetadata $resourceMetadata): Response
83
    {
84
        $serializerGroups = $resourceMetadata->getOperationAttribute(
85
            ['item_operation_name' => $requestMethod],
86
            'serializer_groups',
87
            [],
88
            true
89
        );
90
//        $customGroups = $this->apiContextBuilder->getGroups($routeParams['_api_resource_class'], true);
91
//        if (\count($customGroups)) {
92
//            $serializerGroups = array_merge($serializerGroups ?? [], ...$customGroups);
93
//        }
94
        $serializedData = $this->serializer->serialize($entity, $_format, ['groups' => $serializerGroups]);
95
96
        return new Response($serializedData, Response::HTTP_OK);
97
    }
98
99
    private function handleFileUpload(Request $request, FileInterface $entity, string $field, string $requestMethod): void
100
    {
101
        if (!$request->files->count()) {
102
            throw new \InvalidArgumentException('No files have been submitted');
103
        }
104
105
        $files = $request->files->all();
106
        $this->fileUploader->upload($entity, $field, reset($files), $requestMethod);
107
    }
108
109
    private function getFileResponse(object $entity, string $field): BinaryFileResponse
110
    {
111
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
112
        $filePath = $propertyAccessor->getValue($entity, $field);
113
114
        return new BinaryFileResponse($filePath);
115
    }
116
117
    private function getEntityByRouteParams(array $routeParams): FileInterface
118
    {
119
        $resourceClass = $routeParams['_api_resource_class'];
120
        $resourceId = $routeParams['id'];
121
        try {
122
            $entity = $this->itemDataProvider->getItem($resourceClass, $resourceId);
123
        } catch (ResourceClassNotSupportedException $exception) {
124
            throw new \InvalidArgumentException($exception->getMessage());
125
        }
126
        if (!$entity) {
127
            $message = sprintf('Entity not found from provider %s (ID: %s)', $resourceClass, $resourceId);
128
            throw new \InvalidArgumentException($message);
129
        }
130
        if (!($entity instanceof FileInterface)) {
131
            $message = sprintf('Provider %s does not implement %s', $resourceClass, FileInterface::class);
132
            throw new \InvalidArgumentException($message);
133
        }
134
135
        return $entity;
136
    }
137
138
    private function getRouteParamsByIri(Request $request, string $id): array
139
    {
140
        $ctx = new RequestContext();
141
        $ctx->fromRequest($request);
142
        $ctx->setMethod('GET');
143
        $this->urlMatcher->setContext($ctx);
144
        $route = $this->urlMatcher->match($id);
145
        if (empty($route) || !isset($route['_api_resource_class'])) {
146
            $message = sprintf('No route/resource found for id %s', $id);
147
            throw new \InvalidArgumentException($message);
148
        }
149
150
        return $route;
151
    }
152
}
153