Passed
Push — master ( 991457...946f98 )
by Anatoly
02:57 queued 15s
created

UploadedFile::moveTo()   B

Complexity

Conditions 11
Paths 12

Size

Total Lines 40
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.0935

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 23
c 1
b 0
f 0
nc 12
nop 1
dl 0
loc 40
ccs 19
cts 24
cp 0.7917
crap 12.0935
rs 7.3166

How to fix   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
use Psr\Http\Message\StreamInterface;
15
use Psr\Http\Message\UploadedFileInterface;
16
use Sunrise\Http\Message\Exception\RuntimeException;
17
use Throwable;
18
use TypeError;
19
20
use function dirname;
21
use function gettype;
22
use function is_dir;
23
use function is_file;
24
use function is_readable;
25
use function is_string;
26
use function is_uploaded_file;
27
use function is_writable;
28
use function move_uploaded_file;
29
use function rename;
30
use function sprintf;
31
32
use const UPLOAD_ERR_OK;
33
use const UPLOAD_ERR_INI_SIZE;
34
use const UPLOAD_ERR_FORM_SIZE;
35
use const UPLOAD_ERR_PARTIAL;
36
use const UPLOAD_ERR_NO_FILE;
37
use const UPLOAD_ERR_NO_TMP_DIR;
38
use const UPLOAD_ERR_CANT_WRITE;
39
use const UPLOAD_ERR_EXTENSION;
40
41
class UploadedFile implements UploadedFileInterface
42
{
43
    /**
44
     * @link https://www.php.net/manual/en/features.file-upload.errors.php
45
     *
46
     * @var array<int, non-empty-string>
47
     */
48
    public const UPLOAD_ERRORS = [
49
        UPLOAD_ERR_OK         => 'No error',
50
        UPLOAD_ERR_INI_SIZE   => 'Uploaded file exceeds the upload_max_filesize directive in the php.ini',
51
        UPLOAD_ERR_FORM_SIZE  => 'Uploaded file exceeds the MAX_FILE_SIZE directive in the HTML form',
52
        UPLOAD_ERR_PARTIAL    => 'Uploaded file was only partially uploaded',
53
        UPLOAD_ERR_NO_FILE    => 'No file was uploaded',
54
        UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary directory',
55
        UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
56
        UPLOAD_ERR_EXTENSION  => 'File upload was stopped by a PHP extension',
57
    ];
58
59
    private ?StreamInterface $stream;
60
    private ?int $size;
61
    private int $errorCode;
62
    private string $errorMessage;
63
    private ?string $clientFilename;
64
    private ?string $clientMediaType;
65
    private bool $isMoved = false;
66
67 40
    public function __construct(
68
        ?StreamInterface $stream,
69
        ?int $size = null,
70
        int $error = UPLOAD_ERR_OK,
71
        ?string $clientFilename = null,
72
        ?string $clientMediaType = null
73
    ) {
74 40
        $this->stream = $stream;
75 40
        $this->size = $size;
76 40
        $this->errorCode = $error;
77 40
        $this->errorMessage = self::UPLOAD_ERRORS[$error] ?? 'Unknown file upload error';
78 40
        $this->clientFilename = $clientFilename;
79 40
        $this->clientMediaType = $clientMediaType;
80
    }
81
82
    /**
83
     * @inheritDoc
84
     */
85 34
    public function getStream(): StreamInterface
86
    {
87 34
        if ($this->isMoved) {
88 4
            throw new RuntimeException('Uploaded file was moved');
89
        }
90
91 34
        if ($this->errorCode !== UPLOAD_ERR_OK) {
92 16
            throw new RuntimeException($this->errorMessage, $this->errorCode);
93
        }
94
95 18
        if ($this->stream === null) {
96 2
            throw new RuntimeException('Uploaded file has no stream');
97
        }
98
99 16
        return $this->stream;
100
    }
101
102
    /**
103
     * @inheritDoc
104
     */
105 19
    public function moveTo($targetPath): void
106
    {
107
        /** @psalm-suppress TypeDoesNotContainType */
108 19
        if (!is_string($targetPath)) {
109 1
            throw new TypeError(sprintf(
110 1
                'Argument #1 ($targetPath) must be of type string, %s given',
111 1
                gettype($targetPath),
112 1
            ));
113
        }
114
115 18
        $sourceStream = $this->getStream();
116
117 9
        $sourcePath = $sourceStream->getMetadata('uri');
118 9
        if (!is_string($sourcePath) || !is_file($sourcePath) || !is_readable($sourcePath)) {
119 1
            throw new RuntimeException('Uploaded file does not exist or is not readable');
120
        }
121
122 8
        $sourceDirname = dirname($sourcePath);
123 8
        if (!is_writable($sourceDirname)) {
124
            throw new RuntimeException('To move the uploaded file, the source directory must be writable');
125
        }
126
127 8
        $targetDirname = dirname($targetPath);
128 8
        if (!is_dir($targetDirname) || !is_writable($targetDirname)) {
129
            throw new RuntimeException('To move the uploaded file, the target directory must exist and be writable');
130
        }
131
132
        try {
133 8
            $this->isMoved = is_uploaded_file($sourcePath)
134
                ? move_uploaded_file($sourcePath, $targetPath)
135 8
                : rename($sourcePath, $targetPath);
136
        } catch (Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
137
        }
138
139 8
        if (!$this->isMoved) {
140
            throw new RuntimeException('Failed to move the uploaded file');
141
        }
142
143 8
        $sourceStream->close();
144 8
        $this->stream = null;
145
    }
146
147
    /**
148
     * @inheritDoc
149
     */
150 6
    public function getSize(): ?int
151
    {
152 6
        return $this->size;
153
    }
154
155
    /**
156
     * @inheritDoc
157
     */
158 7
    public function getError(): int
159
    {
160 7
        return $this->errorCode;
161
    }
162
163
    /**
164
     * @inheritDoc
165
     */
166 7
    public function getClientFilename(): ?string
167
    {
168 7
        return $this->clientFilename;
169
    }
170
171
    /**
172
     * @inheritDoc
173
     */
174 7
    public function getClientMediaType(): ?string
175
    {
176 7
        return $this->clientMediaType;
177
    }
178
}
179