Passed
Pull Request — master (#27)
by Anatoly
39:10
created

UploadedFile::moveTo()   B

Complexity

Conditions 11
Paths 16

Size

Total Lines 53
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 35
CRAP Score 11

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 30
c 1
b 0
f 0
nc 16
nop 1
dl 0
loc 53
ccs 35
cts 35
cp 1
crap 11
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Nekhay <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Nekhay
8
 * @license https://github.com/sunrise-php/http-message/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-message
10
 */
11
12
namespace Sunrise\Http\Message;
13
14
/**
15
 * Import classes
16
 */
17
use Psr\Http\Message\StreamInterface;
18
use Psr\Http\Message\UploadedFileInterface;
19
use Sunrise\Http\Message\Exception\FailedUploadedFileOperationException;
20
use Sunrise\Http\Message\Exception\InvalidUploadedFileException;
21
use Sunrise\Http\Message\Exception\InvalidUploadedFileOperationException;
22
use Sunrise\Http\Message\Stream\FileStream;
23
24
/**
25
 * Import functions
26
 */
27
use function dirname;
28
use function is_dir;
29
use function is_file;
30
use function is_writable;
31
use function sprintf;
32
use function unlink;
33
34
/**
35
 * Import constants
36
 */
37
use const UPLOAD_ERR_OK;
38
use const UPLOAD_ERR_INI_SIZE;
39
use const UPLOAD_ERR_FORM_SIZE;
40
use const UPLOAD_ERR_PARTIAL;
41
use const UPLOAD_ERR_NO_FILE;
42
use const UPLOAD_ERR_NO_TMP_DIR;
43
use const UPLOAD_ERR_CANT_WRITE;
44
use const UPLOAD_ERR_EXTENSION;
45
46
/**
47
 * UploadedFile
48
 *
49
 * @link https://www.php-fig.org/psr/psr-7/
50
 */
51
class UploadedFile implements UploadedFileInterface
52
{
53
54
    /**
55
     * List of upload errors
56
     *
57
     * @link https://www.php.net/manual/en/features.file-upload.errors.php
58
     *
59
     * @var array<int, string>
60
     */
61
    public const UPLOAD_ERRORS = [
62
        UPLOAD_ERR_OK         => 'No error',
63
        UPLOAD_ERR_INI_SIZE   => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
64
        UPLOAD_ERR_FORM_SIZE  => 'The uploaded file exceeds the MAX_FILE_SIZE directive ' .
65
                                 'that was specified in the HTML form',
66
        UPLOAD_ERR_PARTIAL    => 'The uploaded file was only partially uploaded',
67
        UPLOAD_ERR_NO_FILE    => 'No file was uploaded',
68
        UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
69
        UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
70
        UPLOAD_ERR_EXTENSION  => 'File upload stopped by extension',
71
    ];
72
73
    /**
74
     * Unknown error description
75
     *
76
     * @var string
77
     */
78
    public const UNKNOWN_ERROR_TEXT = 'Unknown error';
79
80
    /**
81
     * The file stream
82
     *
83
     * @var StreamInterface|null
84
     */
85
    private ?StreamInterface $stream = null;
86
87
    /**
88
     * The file size
89
     *
90
     * @var int|null
91
     */
92
    private ?int $size;
93
94
    /**
95
     * The file's error code
96
     *
97
     * @var int
98
     */
99
    private int $errorCode;
100
101
    /**
102
     * The file's error message
103
     *
104
     * @var string
105
     */
106
    private string $errorMessage;
107
108
    /**
109
     * The file name
110
     *
111
     * @var string|null
112
     */
113
    private ?string $clientFilename;
114
115
    /**
116
     * The file type
117
     *
118
     * @var string|null
119
     */
120
    private ?string $clientMediaType;
121
122
    /**
123
     * Constructor of the class
124
     *
125
     * @param StreamInterface $stream
126
     * @param int|null $size
127
     * @param int $error
128
     * @param string|null $clientFilename
129
     * @param string|null $clientMediaType
130
     */
131 38
    public function __construct(
132
        StreamInterface $stream,
133
        ?int $size = null,
134
        int $error = UPLOAD_ERR_OK,
135
        ?string $clientFilename = null,
136
        ?string $clientMediaType = null
137
    ) {
138 38
        if (UPLOAD_ERR_OK === $error) {
139 22
            $this->stream = $stream;
140
        }
141
142 38
        $errorMessage = self::UPLOAD_ERRORS[$error] ?? self::UNKNOWN_ERROR_TEXT;
143
144 38
        $this->size = $size;
145 38
        $this->errorCode = $error;
146 38
        $this->errorMessage = $errorMessage;
147 38
        $this->clientFilename = $clientFilename;
148 38
        $this->clientMediaType = $clientMediaType;
149
    }
150
151
    /**
152
     * Gets the file stream
153
     *
154
     * @return StreamInterface
155
     *
156
     * @throws InvalidUploadedFileException
157
     *         If the file has no a stream due to an error or
158
     *         if the file was already moved.
159
     */
160 17
    public function getStream(): StreamInterface
161
    {
162 17
        if (UPLOAD_ERR_OK <> $this->errorCode) {
163 8
            throw new InvalidUploadedFileException(sprintf(
164 8
                'The uploaded file has no a stream due to the error #%d (%s)',
165 8
                $this->errorCode,
166 8
                $this->errorMessage
167 8
            ));
168
        }
169
170 9
        if (!isset($this->stream)) {
171 2
            throw new InvalidUploadedFileException(
172 2
                'The uploaded file has no a stream because it was already moved'
173 2
            );
174
        }
175
176 7
        return $this->stream;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->stream could return the type null which is incompatible with the type-hinted return Psr\Http\Message\StreamInterface. Consider adding an additional type-check to rule them out.
Loading history...
177
    }
178
179
    /**
180
     * Moves the file to the given path
181
     *
182
     * @param string $targetPath
183
     *
184
     * @return void
185
     *
186
     * @throws InvalidUploadedFileException
187
     *         If the file has no a stream due to an error or
188
     *         if the file was already moved.
189
     *
190
     * @throws InvalidUploadedFileOperationException
191
     *         If the file cannot be read.
192
     *
193
     * @throws FailedUploadedFileOperationException
194
     *         If the target path cannot be used.
195
     */
196 17
    public function moveTo($targetPath): void
197
    {
198 17
        if (UPLOAD_ERR_OK <> $this->errorCode) {
199 8
            throw new InvalidUploadedFileException(sprintf(
200 8
                'The uploaded file cannot be moved due to the error #%d (%s)',
201 8
                $this->errorCode,
202 8
                $this->errorMessage
203 8
            ));
204
        }
205
206 9
        if (!isset($this->stream)) {
207 2
            throw new InvalidUploadedFileException(
208 2
                'The uploaded file cannot be moved because it was already moved'
209 2
            );
210
        }
211
212 9
        if (!$this->stream->isReadable()) {
0 ignored issues
show
Bug introduced by
The method isReadable() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

212
        if (!$this->stream->/** @scrutinizer ignore-call */ isReadable()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
213 1
            throw new InvalidUploadedFileOperationException(
214 1
                'The uploaded file cannot be moved because it is not readable'
215 1
            );
216
        }
217
218 8
        $targetDir = dirname($targetPath);
219 8
        if (!is_dir($targetDir) || !is_writable($targetDir)) {
220 1
            throw new FailedUploadedFileOperationException(sprintf(
221 1
                'The uploaded file cannot be moved because the directory "%s" is not writable',
222 1
                $targetDir
223 1
            ));
224
        }
225
226 7
        $targetStream = new FileStream($targetPath, 'wb');
227
228 7
        if ($this->stream->isSeekable()) {
229 7
            $this->stream->rewind();
230
        }
231
232 7
        while (!$this->stream->eof()) {
233 7
            $piece = $this->stream->read(4096);
234 7
            $targetStream->write($piece);
235
        }
236
237 7
        $targetStream->close();
238
239
        /** @var string|null */
240 7
        $sourcePath = $this->stream->getMetadata('uri');
241
242 7
        $this->stream->close();
243 7
        $this->stream = null;
244
245 7
        if (isset($sourcePath) && is_file($sourcePath)) {
0 ignored issues
show
Bug introduced by
It seems like $sourcePath can also be of type array; however, parameter $filename of is_file() 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

245
        if (isset($sourcePath) && is_file(/** @scrutinizer ignore-type */ $sourcePath)) {
Loading history...
246 1
            $sourceDir = dirname($sourcePath);
0 ignored issues
show
Bug introduced by
It seems like $sourcePath can also be of type array; however, parameter $path of dirname() 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

246
            $sourceDir = dirname(/** @scrutinizer ignore-type */ $sourcePath);
Loading history...
247 1
            if (is_writable($sourceDir)) {
248 1
                unlink($sourcePath);
0 ignored issues
show
Bug introduced by
It seems like $sourcePath can also be of type array; however, parameter $filename of unlink() 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

248
                unlink(/** @scrutinizer ignore-type */ $sourcePath);
Loading history...
249
            }
250
        }
251
    }
252
253
    /**
254
     * Gets the file size
255
     *
256
     * @return int|null
257
     */
258 7
    public function getSize(): ?int
259
    {
260 7
        return $this->size;
261
    }
262
263
    /**
264
     * Gets the file's error code
265
     *
266
     * @return int
267
     */
268 7
    public function getError(): int
269
    {
270 7
        return $this->errorCode;
271
    }
272
273
    /**
274
     * Gets the file name
275
     *
276
     * @return string|null
277
     */
278 7
    public function getClientFilename(): ?string
279
    {
280 7
        return $this->clientFilename;
281
    }
282
283
    /**
284
     * Gets the file type
285
     *
286
     * @return string|null
287
     */
288 7
    public function getClientMediaType(): ?string
289
    {
290 7
        return $this->clientMediaType;
291
    }
292
}
293