Completed
Pull Request — develop (#576)
by
unknown
20:41
created

FileManager   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 10
dl 0
loc 289
ccs 0
cts 159
cp 0
rs 8.6206
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A setAllowedMimeTypes() 0 4 1
B buildGetContentResponse() 0 32 5
A saveFile() 0 10 1
D handleSaveRequest() 0 63 14
D buildFileDocument() 0 47 16
A validIdRequest() 0 10 4
A remove() 0 6 2
B getUploadedFileFromRequest() 0 17 6

How to fix   Complexity   

Complex Class

Complex classes like FileManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FileManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Handles file specific actions
4
 */
5
6
namespace Graviton\FileBundle\Manager;
7
8
use Doctrine\ODM\MongoDB\Id\UuidGenerator;
9
use GravitonDyn\FileBundle\Document\File as FileDocument;
10
use GravitonDyn\FileBundle\Document\FileMetadataBase;
11
use GravitonDyn\FileBundle\Document\FileMetadataEmbedded;
12
use League\Flysystem\Filesystem;
13
use Symfony\Component\Form\Exception\InvalidArgumentException;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\Response;
16
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
17
use Symfony\Component\HttpFoundation\FileBag;
18
use Symfony\Component\HttpFoundation\File\UploadedFile;
19
use GravitonDyn\FileBundle\Document\File as DocumentFile;
20
use Doctrine\Bundle\MongoDBBundle\ManagerRegistry;
21
use Doctrine\ODM\MongoDB\DocumentManager;
22
use Symfony\Component\Filesystem\Filesystem as SfFileSystem;
23
use GravitonDyn\FileBundle\Model\File as DocumentModel;
24
use Graviton\ExceptionBundle\Exception\NotFoundException;
25
26
/**
27
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
28
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
29
 * @link     http://swisscom.ch
30
 */
31
class FileManager
32
{
33
    /**
34
     * @var Filesystem
35
     */
36
    private $fileSystem;
37
38
    /**
39
     * @var DocumentManager
40
     */
41
    private $documentManager;
42
43
    /** @var array allowedMimeTypes Control files to be saved and returned */
44
    private $allowedMimeTypes = [];
45
46
    /**
47
     * FileManager constructor.
48
     *
49
     * @param Filesystem      $fileSystem      file system abstraction layer for s3 and more
50
     * @param ManagerRegistry $managerRegistry MongoDB registry manager
51
     */
52
    public function __construct(
53
        Filesystem $fileSystem,
54
        ManagerRegistry $managerRegistry
55
    ) {
56
        $this->fileSystem = $fileSystem;
57
        $this->documentManager = $managerRegistry->getManager();
58
    }
59
60
    /**
61
     * Configure allowed content types, empty is equal to all
62
     *
63
     * @param array $mimeTypes of Allowed types, application/pdf, image/jpeg...
64
     *
65
     * @return void
66
     */
67
    public function setAllowedMimeTypes(array $mimeTypes)
68
    {
69
        $this->allowedMimeTypes = $mimeTypes;
70
    }
71
72
    /**
73
     * Will update the response object with provided file data
74
     *
75
     * @param Response     $response response
76
     * @param DocumentFile $file     File document object from DB
77
     *
78
     * @return Response
79
     * @throws InvalidArgumentException if invalid info fetched from fileSystem
80
     */
81
    public function buildGetContentResponse(Response $response, FileDocument $file)
82
    {
83
        /** @var FileMetadataBase $metadata */
84
        $metadata = $file->getMetadata();
85
        if (!$metadata) {
86
            throw new InvalidArgumentException('Loaded file have no valid metadata');
87
        }
88
89
        // We use file's mimeType, just in case none we use DB's.
90
        $mimeType = $this->fileSystem->getMimetype($file->getId());
91
        if (!$mimeType) {
92
            $mimeType = $metadata->getMime();
93
        }
94
        if ($this->allowedMimeTypes && !in_array($mimeType, $this->allowedMimeTypes)) {
95
            throw new InvalidArgumentException('File mime type: '.$mimeType.' is not allowed as response.');
96
        }
97
98
        // Create Response
99
        $disposition = $response->headers->makeDisposition(
100
            ResponseHeaderBag::DISPOSITION_INLINE,
101
            $metadata->getFilename()
102
        );
103
104
        $response
105
            ->setStatusCode(Response::HTTP_OK)
106
            ->setContent($this->fileSystem->read($file->getId()));
107
        $response
108
            ->headers->set('Content-Type', $mimeType);
109
        $response
110
            ->headers->set('Content-Disposition', $disposition);
111
        return $response;
112
    }
113
114
115
    /**
116
     * Save or update a file
117
     *
118
     * @param string $id       ID of file
119
     * @param String $filepath path to the file to save
120
     *
121
     * @return void
122
     */
123
    public function saveFile($id, $filepath)
124
    {
125
        // will save using a stream
126
        $fp = fopen($filepath, 'r+');
127
128
        $this->fileSystem->putStream($id, $fp);
129
130
        // close file
131
        fclose($fp);
132
    }
133
134
    /**
135
     * @param DocumentFile  $document File Document
136
     * @param Request       $request  Request bag
137
     * @param DocumentModel $model    File Document Model
138
     * @return DocumentFile
139
     */
140
    public function handleSaveRequest(
141
        FileDocument $document,
142
        Request $request,
143
        DocumentModel $model
144
    ) {
145
        $file = $this->getUploadedFileFromRequest($request);
146
        $requestId = $request->get('id', '');
147
        if ($requestId && !$document->getId()) {
148
            $document->setId($requestId);
149
        }
150
151
        try {
152
            $original = $model->find($requestId);
153
        } catch (NotFoundException $e) {
154
            $original = false;
155
        }
156
157
        $isNew = $requestId ? !$original : true;
158
159
        // If posted  file document not equal the one to be created or updated, then error
160
        if (!$this->validIdRequest($document, $requestId)) {
161
            throw new InvalidArgumentException('File id and Request id must match.');
162
        }
163
164
        $document = $this->buildFileDocument($document, $file, $original);
165
        if (!$document->getId()) {
166
            $n = new UuidGenerator();
167
            $uuid = (string) $n->generateV4();
168
            $document->setId($uuid);
169
        }
170
171
        // Filename limitation
172
        if ($filename = $document->getMetadata()->getFilename()) {
173
            // None English chars
174
            if (preg_match('/[^a-z_\-0-9.]/i', $filename)) {
175
                throw new InvalidArgumentException('None special chars allowed for filename, given: '.$filename);
176
            }
177
        }
178
179
        // All ok, let's save the file
180
        if ($isNew) {
181
            if (!$file || $file->getSize() == 0) {
182
                throw new InvalidArgumentException('You can not create a new empty file resource. No file received.');
183
            }
184
        }
185
186
        if ($file) {
187
            $this->saveFile($document->getId(), $file->getRealPath());
188
            $sfFileSys = new SfFileSystem();
189
            $sfFileSys->remove($file->getRealPath());
190
        }
191
192
        if ($isNew) {
193
            $model->insertRecord($document);
194
        } else {
195
            $model->updateRecord($document->getId(), $document);
196
        }
197
198
        // store id of new record so we don't need to re-parse body later when needed
199
        $request->attributes->set('id', $document->getId());
200
201
        return $document;
202
    }
203
204
    /**
205
     * Create the basic needs for a file
206
     *
207
     * @param DocumentFile $document Post or Put file document
208
     * @param UploadedFile $file     To be used in set metadata
209
     * @param DocumentFile $original If there is a original document
210
     *
211
     * @return DocumentFile
212
     * @throws InvalidArgumentException
213
     */
214
    private function buildFileDocument(FileDocument $document, $file, $original)
215
    {
216
        $now = new \DateTime();
217
218
        // If only a file is posted, check if there is a original object and clone it
219
        if ($file && $original && !$document->getMetadata()) {
220
            $document = clone $original;
221
        }
222
223
        // Basic Metadata update
224
        $metadata = $document->getMetadata() ?: new FileMetadataEmbedded();
225
226
        // File related, if no file uploaded we keep original file info.
227
        if ($file) {
228
            $hash = $metadata->getHash();
229
            if (!$hash || strlen($hash)>64) {
230
                $hash = hash('sha256', file_get_contents($file->getRealPath()));
231
            }
232
            $metadata->setHash($hash);
233
            $metadata->setMime($file->getMimeType());
234
            $metadata->setSize($file->getSize());
235
            if (!$metadata->getFilename()) {
236
                $fileName = $file->getClientOriginalName() ? $file->getClientOriginalName() : $file->getFilename();
237
                $fileName = preg_replace("/[^a-zA-Z0-9.]/", "-", $fileName);
238
                $metadata->setFilename($fileName);
239
            }
240
        } elseif ($original && ($originalMetadata = $original->getMetadata())) {
241
            if (!$metadata->getFilename()) {
242
                $metadata->setFilename($originalMetadata->getFilename());
243
            }
244
            $metadata->setHash($originalMetadata->getHash());
245
            $metadata->setMime($originalMetadata->getMime());
246
            $metadata->setSize($originalMetadata->getSize());
247
        }
248
249
        // Creation date. keep original if available
250
        if ($original && $original->getMetadata() && $original->getMetadata()->getCreatedate()) {
251
            $metadata->setCreatedate($original->getMetadata()->getCreatedate());
252
        } else {
253
            $metadata->setCreatedate($now);
254
        }
255
256
        $metadata->setModificationdate($now);
257
        $document->setMetadata($metadata);
258
259
        return $document;
260
    }
261
262
    /**
263
     * Simple validation for post/put request
264
     *
265
     * @param DocumentFile $document  File document
266
     * @param string       $requestId Request ID
267
     * @return bool
268
     */
269
    private function validIdRequest(FileDocument $document, $requestId)
0 ignored issues
show
Coding Style introduced by
function validIdRequest() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
270
    {
271
        if (!$requestId && !$document->getId()) {
272
            return true;
273
        }
274
        if ($requestId === $document->getId()) {
275
            return true;
276
        }
277
        return false;
278
    }
279
280
    /**
281
     * Simple delete item from file system
282
     *
283
     * @param string $id ID of file to be deleted
284
     *
285
     * @return void
286
     */
287
    public function remove($id)
288
    {
289
        if ($this->fileSystem->has($id)) {
290
            $this->fileSystem->delete($id);
291
        }
292
    }
293
294
    /**
295
     * Set global uploaded file.
296
     * Only ONE file allowed per upload.
297
     *
298
     * @param Request $request service request
299
     * @return UploadedFile if file was uploaded
300
     * @throws InvalidArgumentException
301
     */
302
    private function getUploadedFileFromRequest(Request $request)
303
    {
304
        $file = false;
305
306
        if ($request->files instanceof FileBag && $request->files->count() > 0) {
307
            if ($request->files->count() > 1) {
308
                throw new InvalidArgumentException('Only 1 file upload per requests allowed.');
309
            }
310
            $files = $request->files->all();
311
            $file = reset($files);
312
            if ($this->allowedMimeTypes && !in_array($file->getMimeType(), $this->allowedMimeTypes)) {
313
                throw new InvalidArgumentException('File mime type: '.$file->getMimeType().' is not allowed.');
314
            }
315
        }
316
317
        return $file;
318
    }
319
}
320