Completed
Push — feature/other-validation ( a3189f...c451b7 )
by Narcotic
13:21
created

FileController::validateFileRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
ccs 0
cts 7
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 3
crap 6
1
<?php
2
/**
3
 * controller for gaufrette based file store
4
 */
5
6
namespace Graviton\FileBundle\Controller;
7
8
use Graviton\FileBundle\FileManager;
9
use Graviton\RestBundle\Controller\RestController;
10
use Graviton\RestBundle\Service\RestUtilsInterface;
11
use Graviton\SchemaBundle\SchemaUtils;
12
use GravitonDyn\FileBundle\Document\File;
13
use Symfony\Component\DependencyInjection\ContainerInterface;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Bundle\FrameworkBundle\Routing\Router;
17
use Symfony\Component\Validator\Validator\ValidatorInterface;
18
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
19
use Symfony\Component\Form\FormFactory;
20
use Graviton\DocumentBundle\Form\Type\DocumentType;
21
22
/**
23
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
24
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
25
 * @link     http://swisscom.ch
26
 */
27
class FileController extends RestController
28
{
29
    /**
30
     * @var FileManager
31
     */
32
    private $fileManager;
33
34
    /**
35
     * @param Response           $response    Response
36
     * @param RestUtilsInterface $restUtils   Rest utils
37
     * @param Router             $router      Router
38
     * @param ValidatorInterface $validator   Validator
39
     * @param EngineInterface    $templating  Templating
40
     * @param FormFactory        $formFactory form factory
41
     * @param DocumentType       $formType    generic form
42
     * @param ContainerInterface $container   Container
43
     * @param SchemaUtils        $schemaUtils schema utils
44
     * @param FileManager        $fileManager Handles file specific tasks
45
     */
46
    public function __construct(
47
        Response $response,
48
        RestUtilsInterface $restUtils,
49
        Router $router,
50
        ValidatorInterface $validator,
51
        EngineInterface $templating,
52
        FormFactory $formFactory,
53
        DocumentType $formType,
54
        ContainerInterface $container,
55
        SchemaUtils $schemaUtils,
56
        FileManager $fileManager
57
    ) {
58
        parent::__construct(
59
            $response,
60
            $restUtils,
61
            $router,
62
            $validator,
63
            $templating,
64
            $formFactory,
65
            $formType,
66
            $container,
67
            $schemaUtils
68
        );
69
        $this->fileManager = $fileManager;
70
    }
71
72
    /**
73
     * Writes a new Entry to the database
74
     *
75
     * @param Request $request Current http request
76
     *
77
     * @return Response $response Result of action with data (if successful)
78
     */
79
    public function postAction(Request $request)
80
    {
81
        $response = $this->getResponse();
82
        $fileData = $this->validateFileRequest($request, $response, $request->get('metadata'));
83
        $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 82 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...
84
85
        // store id of new record so we don't need to re-parse body later when needed
86
        $request->attributes->set('id', $files[0]);
87
88
        // Set status code and content
89
        $response->setStatusCode(Response::HTTP_CREATED);
90
91
        // TODO: this not is correct for multiple uploaded files!!
92
        // TODO: Probably use "Link" header to address this.
93
        $locations = $this->determineRoutes($request->get('_route'), $files, ['post', 'postNoSlash']);
94
        $response->headers->set(
95
            'Location',
96
            $locations[0]
97
        );
98
99
        return $response;
100
    }
101
102
    /**
103
     * respond with document if non json mime-type is requested
104
     *
105
     * @param Request $request Current http request
106
     * @param string  $id      id of file
107
     *
108
     * @return Response
109
     */
110
    public function getAction(Request $request, $id)
111
    {
112
        $accept = $request->headers->get('accept');
113
        if (substr(strtolower($accept), 0, 16) === 'application/json') {
114
            return parent::getAction($request, $id);
115
        }
116
        $response = $this->getResponse();
117
118
        if (!$this->fileManager->has($id)) {
119
            $response->setStatusCode(Response::HTTP_NOT_FOUND);
120
121
            return $response;
122
        }
123
124
        $record = $this->findRecord($id);
125
        $data = $this->fileManager->read($id);
126
127
        $response->setStatusCode(Response::HTTP_OK);
128
        $response->headers->set('Content-Type', $record->getMetadata()->getMime());
129
130
        return $this->render(
131
            'GravitonFileBundle:File:index.raw.twig',
132
            ['data' => $data],
133
            $response
134
        );
135
    }
136
137
    /**
138
     * Update a record
139
     *
140
     * @param Number  $id      ID of record
141
     * @param Request $request Current http request
142
     *
143
     * @return Response $response Result of action with data (if successful)
144
     */
145
    public function putAction($id, Request $request)
146
    {
147
        $contentType = $request->headers->get('Content-Type');
148
        if (substr(strtolower($contentType), 0, 16) === 'application/json') {
149
            return parent::putAction($id, $request);
150
        }
151
        if (0 === strpos($contentType, 'multipart/form-data')) {
152
            $request = $this->normalizeRequest($request);
153
        }
154
155
        $response = $this->getResponse();
156
        $fileData = $this->validateFileRequest($request, $response, $request->get('metadata'));
157
        $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 156 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...
158
159
        // store id of new record so we don't need to re-parse body later when needed
160
        $request->attributes->set('id', $files[0]);
161
162
        $response->setStatusCode(Response::HTTP_NO_CONTENT);
163
164
        // no service sends Location headers on PUT - /file shouldn't as well
165
166
        return $response;
167
    }
168
169
    /**
170
     * Deletes a record
171
     *
172
     * @param Number $id ID of record
173
     *
174
     * @return Response $response Result of the action
175
     */
176
    public function deleteAction($id)
177
    {
178
        if ($this->fileManager->has($id)) {
179
            $this->fileManager->delete($id);
180
        }
181
182
        return parent::deleteAction($id);
183
    }
184
185
    /**
186
     * Determines the routes and replaces the http method
187
     *
188
     * @param string $routeName  Name of the route to be generated
189
     * @param array  $files      Set of uploaded files
190
     * @param array  $routeTypes Set of route types to be recognized
191
     *
192
     * @return array
193
     */
194
    private function determineRoutes($routeName, array $files, array $routeTypes)
195
    {
196
        $locations = [];
197
        $newRouteName = '';
198
        foreach ($routeTypes as $routeType) {
199
            $routeParts = explode('.', $routeName);
200
201
            if ($routeType == array_pop($routeParts)) {
202
                $reduce = (-1) * strlen($routeType);
203
                $newRouteName = substr($routeName, 0, $reduce).'get';
204
                break;
205
            }
206
        }
207
208
        if (!empty($newRouteName)) {
209
            foreach ($files as $id) {
210
                $locations[] = $this->getRouter()->generate($newRouteName, array('id' => $id));
211
            }
212
        }
213
214
        return $locations;
215
    }
216
217
    /**
218
     * Validates the provided request
219
     *
220
     * @param Request  $request  Http request to be validated
221
     * @param Response $response Http response to be returned in case of an error
222
     * @param string   $fileData Alternative content to be validated
223
     *
224
     * @throws \Exception
225
     * @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...
226
     */
227
    protected function validateFileRequest(Request $request, Response $response, $fileData = '')
0 ignored issues
show
Unused Code introduced by
The parameter $response is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
228
    {
229
        if (!empty($fileData)) {
230
            $model = $this->getModel();
231
            return $this->validateRequest($request->getContent(), $model);
0 ignored issues
show
Documentation introduced by
$request->getContent() is of type resource|string, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
232
        }
233
    }
234
235
    /**
236
     * Gathers information into a request
237
     *
238
     * @param Request $request master request sent by client.
239
     *
240
     * @return Request
241
     */
242
    private function normalizeRequest(Request $request)
243
    {
244
        $contentData = $this->fileManager->extractDataFromRequestContent($request);
245
        $normalized = $request->duplicate(
246
            null,
247
            null,
248
            $contentData['attributes'],
249
            null,
250
            $contentData['files']
251
        );
252
253
        return $normalized;
254
    }
255
}
256