Completed
Push — master ( a97424...f904c2 )
by Daniel
14:31
created

FileUploadAction::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 16
ccs 0
cts 3
cp 0
rs 10
cc 1
nc 1
nop 7
crap 2
1
<?php
2
3
namespace Silverback\ApiComponentBundle\Controller;
4
5
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
6
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
7
use InvalidArgumentException;
8
use RuntimeException;
9
use Silverback\ApiComponentBundle\Entity\Component\FileInterface;
10
use Silverback\ApiComponentBundle\Entity\RestrictedResourceInterface;
11
use Silverback\ApiComponentBundle\File\Uploader\FileUploader;
12
use Silverback\ApiComponentBundle\Security\RestrictedResourceVoter;
13
use Silverback\ApiComponentBundle\Serializer\ApiContextBuilder;
14
use Symfony\Component\HttpFoundation\BinaryFileResponse;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\PropertyAccess\PropertyAccess;
18
use Symfony\Component\Routing\Annotation\Route;
19
use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
20
use Symfony\Component\Routing\RequestContext;
21
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
22
use Symfony\Component\Serializer\SerializerInterface;
23
24
class FileUploadAction
25
{
26
    private $urlMatcher;
27
    private $itemDataProvider;
28
    private $uploader;
29
    private $serializer;
30
    private $resourceMetadataFactory;
31
    private $apiContextBuilder;
32
    private $restrictedResourceVoter;
33
34
    public function __construct(
35
        UrlMatcherInterface $urlMatcher,
36
        ItemDataProviderInterface $itemDataProvider,
37
        FileUploader $uploader,
38
        SerializerInterface $serializer,
39
        ResourceMetadataFactoryInterface $resourceMetadataFactory,
40
        ApiContextBuilder $apiContextBuilder,
41
        RestrictedResourceVoter $restrictedResourceVoter
42
    ) {
43
        $this->urlMatcher = $urlMatcher;
44
        $this->itemDataProvider = $itemDataProvider;
45
        $this->uploader = $uploader;
46
        $this->serializer = $serializer;
47
        $this->resourceMetadataFactory = $resourceMetadataFactory;
48
        $this->apiContextBuilder = $apiContextBuilder;
49
        $this->restrictedResourceVoter = $restrictedResourceVoter;
50
    }
51
52
    /**
53
     * @param Request $request
54
     * @param string $field
55
     * @param string $id
56
     * @Route(
57
     *     name="files_upload",
58
     *     path="/files/{field}/{id}.{_format}",
59
     *     requirements={"field"="\w+", "id"=".+"},
60
     *     defaults={"_format"="jsonld"},
61
     *     methods={"POST", "PUT", "GET"}
62
     * )
63
     * @return Response
64
     */
65
    public function __invoke(Request $request, string $field, string $id)
66
    {
67
        $contentType = $request->headers->get('CONTENT_TYPE');
68
        $_format = $request->attributes->get('_format') ?: $request->getFormat($contentType);
69
70
        /**
71
         * MATCH THE ID TO A ROUTE TO FIND RESOURCE CLASS AND ID
72
         * @var array|null $route
73
         */
74
        $ctx = new RequestContext();
75
        $ctx->fromRequest($request);
76
        $ctx->setMethod('GET');
77
        $this->urlMatcher->setContext($ctx);
78
        $route = $this->urlMatcher->match($id);
79
        if (empty($route) || !isset($route['_api_resource_class'])) {
80
            return new Response(sprintf('No route/resource found for id %s', $id), Response::HTTP_BAD_REQUEST);
81
        }
82
83
        /**
84
         * GET THE ENTITY
85
         */
86
        $entity = $this->itemDataProvider->getItem($route['_api_resource_class'], $route['id']);
87
        if (!$entity) {
88
            return new Response(sprintf('Entity not found from provider %s (ID: %s)', $route['_api_resource_class'], $route['id']), Response::HTTP_BAD_REQUEST);
89
        }
90
        if (!($entity instanceof FileInterface)) {
91
            return new Response(sprintf('Provider %s does not implement %s', $route['_api_resource_class'], FileInterface::class), Response::HTTP_BAD_REQUEST);
92
        }
93
        $method = strtolower($request->getMethod());
94
95
        if ($method === 'get') {
96
            $propertyAccessor = PropertyAccess::createPropertyAccessor();
97
            if (!$this->restrictedResourceVoter->vote($entity)) {
98
                throw new AccessDeniedException('You are not permitted to download this file');
99
            }
100
            $filePath = $propertyAccessor->getValue($entity, $field);
101
            return new BinaryFileResponse($filePath);
102
        }
103
104
        /**
105
         * CHECK WE HAVE A FILE - WASTE OF TIME DOING ANYTHING ELSE OTHERWISE
106
         */
107
        if (!$request->files->count()) {
108
            return new Response('No files have been submitted', Response::HTTP_BAD_REQUEST);
109
        }
110
111
        /**
112
         * UPLOAD THE FILE
113
         */
114
        $files = $request->files->all();
115
        try {
116
            $entity = $this->uploader->upload($entity, $field, reset($files), $method);
117
        } catch (InvalidArgumentException $exception) {
118
            return new Response($exception->getMessage(), Response::HTTP_BAD_REQUEST);
119
        } catch (RuntimeException $exception) {
120
            return new Response($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
121
        }
122
123
        /**
124
         * Return the entity back in the format requested
125
         */
126
        $resourceMetadata = $this->resourceMetadataFactory->create($route['_api_resource_class']);
127
        $serializerGroups = $resourceMetadata->getOperationAttribute(
128
            ['item_operation_name' => $method],
129
            'serializer_groups',
130
            [],
131
            true
132
        );
133
        $customGroups = $this->apiContextBuilder->getGroups($route['_api_resource_class'], true);
134
        if (\count($customGroups)) {
135
            $serializerGroups = array_merge($serializerGroups ?? [], ...$customGroups);
136
        }
137
        $serializedData = $this->serializer->serialize($entity, $_format, ['groups' => $serializerGroups]);
138
        return new Response($serializedData, Response::HTTP_OK);
139
    }
140
}
141