UploadedFile::moveTo()   B
last analyzed

Complexity

Conditions 11
Paths 12

Size

Total Lines 41
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 12.0935

Importance

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