Completed
Push — feature/EVO-7957-file-upload-d... ( dbe76d )
by
unknown
84:46 queued 74:55
created

FileController::patchAction()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 31
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 0
cts 27
cp 0
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 21
nc 3
nop 2
crap 12
1
<?php
2
/**
3
 * controller for gaufrette based file store
4
 */
5
6
namespace Graviton\FileBundle\Controller;
7
8
use Graviton\ExceptionBundle\Exception\MalformedInputException;
9
use Graviton\FileBundle\FileManager;
10
use Graviton\RestBundle\Controller\RestController;
11
use Graviton\RestBundle\Service\RestUtilsInterface;
12
use Graviton\SchemaBundle\SchemaUtils;
13
use GravitonDyn\FileBundle\Document\File;
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Bundle\FrameworkBundle\Routing\Router;
18
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
19
20
/**
21
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
22
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
23
 * @link     http://swisscom.ch
24
 */
25
class FileController extends RestController
26
{
27
    /**
28
     * @var FileManager
29
     */
30
    private $fileManager;
31
32
    /**
33
     * @param Response           $response    Response
34
     * @param RestUtilsInterface $restUtils   Rest utils
35
     * @param Router             $router      Router
36
     * @param EngineInterface    $templating  Templating
37
     * @param ContainerInterface $container   Container
38
     * @param SchemaUtils        $schemaUtils schema utils
39
     * @param FileManager        $fileManager Handles file specific tasks
40
     */
41
    public function __construct(
42
        Response $response,
43
        RestUtilsInterface $restUtils,
44
        Router $router,
45
        EngineInterface $templating,
46
        ContainerInterface $container,
47
        SchemaUtils $schemaUtils,
48
        FileManager $fileManager
49
    ) {
50
        parent::__construct(
51
            $response,
52
            $restUtils,
53
            $router,
54
            $templating,
55
            $container,
56
            $schemaUtils
57
        );
58
        $this->fileManager = $fileManager;
59
    }
60
61
    /**
62
     * Writes a new Entry to the database
63
     *
64
     * @param Request $request Current http request
65
     *
66
     * @return Response $response Result of action with data (if successful)
67
     */
68
    public function postAction(Request $request)
69
    {
70
        $response = $this->getResponse();
71
        $fileData = $this->validateFileRequest($request->get('metadata'));
72
        $files = $this->fileManager->saveFiles($request, $this->getModel(), $fileData);
0 ignored issues
show
Bug introduced by
It seems like $fileData defined by $this->validateFileReque...quest->get('metadata')) on line 71 can also be of type object; however, Graviton\FileBundle\FileManager::saveFiles() does only seem to accept null|object<GravitonDyn\FileBundle\Document\File>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
73
74
        // store id of new record so we don't need to re-parse body later when needed
75
        $request->attributes->set('id', $files[0]);
76
77
        // Set status code and content
78
        $response->setStatusCode(Response::HTTP_CREATED);
79
80
        // TODO: this not is correct for multiple uploaded files!!
81
        // TODO: Probably use "Link" header to address this.
82
        $locations = $this->determineRoutes($request->get('_route'), $files, ['post', 'postNoSlash']);
83
        $response->headers->set(
84
            'Location',
85
            $locations[0]
86
        );
87
88
        return $response;
89
    }
90
91
    /**
92
     * respond with document if non json mime-type is requested
93
     *
94
     * @param Request $request Current http request
95
     * @param string  $id      id of file
96
     *
97
     * @return Response
98
     */
99
    public function getAction(Request $request, $id)
100
    {
101
        $accept = $request->headers->get('accept');
102
        if (substr(strtolower($accept), 0, 16) === 'application/json') {
103
            return parent::getAction($request, $id);
104
        }
105
        $response = $this->getResponse();
106
107
        if (!$this->fileManager->has($id)) {
108
            $response->setStatusCode(Response::HTTP_NOT_FOUND);
109
110
            return $response;
111
        }
112
113
        $record = $this->findRecord($id);
114
        $data = $this->fileManager->read($id);
115
116
        $response->setStatusCode(Response::HTTP_OK);
117
        $response->headers->set('Content-Type', $record->getMetadata()->getMime());
118
119
        return $this->render(
120
            'GravitonFileBundle:File:index.raw.twig',
121
            ['data' => $data],
122
            $response
123
        );
124
    }
125
126
    /**
127
     * Update a record
128
     *
129
     * @param Number  $id      ID of record
130
     * @param Request $request Current http request
131
     *
132
     * @return Response $response Result of action with data (if successful)
133
     */
134
    public function putAction($id, Request $request)
135
    {
136
        $contentType = $request->headers->get('Content-Type');
137
        if (substr(strtolower($contentType), 0, 16) === 'application/json') {
138
            return parent::putAction($id, $request);
139
        }
140
        if (0 === strpos($contentType, 'multipart/form-data')) {
141
            $request = $this->normalizeRequest($request);
142
        }
143
144
        $response = $this->getResponse();
145
        $fileData = $this->validateFileRequest($request->get('metadata'));
146
        $files = $this->fileManager->saveFiles($request, $this->getModel(), $fileData);
0 ignored issues
show
Bug introduced by
It seems like $fileData defined by $this->validateFileReque...quest->get('metadata')) on line 145 can also be of type object; however, Graviton\FileBundle\FileManager::saveFiles() does only seem to accept null|object<GravitonDyn\FileBundle\Document\File>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
147
148
        // store id of new record so we don't need to re-parse body later when needed
149
        $request->attributes->set('id', $files[0]);
150
151
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
152
153
        // no service sends Location headers on PUT - /file shouldn't as well
154
155
        return $response;
156
    }
157
158
    /**
159
     * Deletes a record
160
     *
161
     * @param Number $id ID of record
162
     *
163
     * @return Response $response Result of the action
164
     */
165
    public function deleteAction($id)
166
    {
167
        if ($this->fileManager->has($id)) {
168
            $this->fileManager->delete($id);
169
        }
170
171
        return parent::deleteAction($id);
172
    }
173
174
    /**
175
     * Determines the routes and replaces the http method
176
     *
177
     * @param string $routeName  Name of the route to be generated
178
     * @param array  $files      Set of uploaded files
179
     * @param array  $routeTypes Set of route types to be recognized
180
     *
181
     * @return array
182
     */
183
    private function determineRoutes($routeName, array $files, array $routeTypes)
184
    {
185
        $locations = [];
186
        $newRouteName = '';
187
        foreach ($routeTypes as $routeType) {
188
            $routeParts = explode('.', $routeName);
189
190
            if ($routeType == array_pop($routeParts)) {
191
                $reduce = (-1) * strlen($routeType);
192
                $newRouteName = substr($routeName, 0, $reduce).'get';
193
                break;
194
            }
195
        }
196
197
        if (!empty($newRouteName)) {
198
            foreach ($files as $id) {
199
                $locations[] = $this->getRouter()->generate($newRouteName, array('id' => $id));
200
            }
201
        }
202
203
        return $locations;
204
    }
205
206
    /**
207
     * Validates the provided request
208
     *
209
     * @param string $fileData Alternative content to be validated
210
     *
211
     * @throws \Exception
212
     * @return File|null
0 ignored issues
show
Documentation introduced by
Should the return type not be object|array|integer|double|string|boolean|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
213
     */
214
    protected function validateFileRequest($fileData)
215
    {
216
        if (!empty($fileData)) {
217
            return $this->validateRequest($fileData, $this->getModel());
218
        }
219
    }
220
221
    /**
222
     * Gathers information into a request
223
     *
224
     * @param Request $request master request sent by client.
225
     *
226
     * @return Request
227
     */
228
    private function normalizeRequest(Request $request)
229
    {
230
        $contentData = $this->fileManager->extractDataFromRequestContent($request);
231
        $normalized = $request->duplicate(
232
            null,
233
            null,
234
            $contentData['attributes'],
235
            null,
236
            $contentData['files']
237
        );
238
239
        return $normalized;
240
    }
241
242
    /**
243
     * Patch a record, we add here a patch on Modification Data.
244
     *
245
     * @param Number  $id      ID of record
246
     * @param Request $request Current http request
247
     *
248
     * @throws MalformedInputException
249
     *
250
     * @return Response $response Result of action with data (if successful)
251
     */
252
    public function patchAction($id, Request $request)
253
    {
254
        // Update modified date
255
        $content = json_decode($request->getContent(), true);
256
        if ($content) {
257
            $now = new \DateTime();
258
            $patch = [
259
                'op' => 'replace',
260
                'path' => '/metadata/modificationDate',
261
                'value' => $now->format(DATE_ISO8601)
262
            ];
263
            // It can be a simple patch or a multi array patching.
264
            if (array_key_exists(0, $content)) {
265
                $content[] = $patch;
266
            } else {
267
                $content = [$content, $patch];
268
            }
269
270
            $request = new Request(
271
                $request->query->all(),
272
                $request->request->all(),
273
                $request->attributes->all(),
274
                $request->cookies->all(),
275
                $request->files->all(),
276
                $request->server->all(),
277
                json_encode($content)
278
            );
279
        }
280
281
        return parent::patchAction($id, $request);
282
    }
283
}
284