UploadedFile::moveTo()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 14
nc 2
nop 1
dl 0
loc 23
rs 9.7998
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of slick/http
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Slick\Http\Message\Server;
13
14
use Psr\Http\Message\StreamInterface;
15
use Psr\Http\Message\UploadedFileInterface;
16
use Slick\Http\Message\Exception\InvalidArgumentException;
17
use Slick\Http\Message\Exception\RuntimeException;
18
use Slick\Http\Message\Stream\FileStream;
19
20
/**
21
 * UploadedFile
22
 *
23
 * @package Slick\Http\Message\Server
24
*/
25
final class UploadedFile implements UploadedFileInterface
26
{
27
    /**
28
     * @var StreamInterface|null
29
     */
30
    private ?StreamInterface $stream = null;
31
32
    /**
33
     * @var int
34
     */
35
    private int $size = 0;
36
37
    /**
38
     * @var int
39
     */
40
    private int $error;
41
42
    /**
43
     * @var string|null
44
     */
45
    private ?string $clientName;
46
47
    /**
48
     * @var null|string
49
     */
50
    private ?string $mediaType;
51
52
    /**
53
     * @var string
54
     */
55
    private string $tmpFile;
56
57
    /**
58
     * @var bool
59
     */
60
    private bool $moved = false;
61
62
    /**
63
     * Creates an UploadedFile
64
     *
65
     * @param StreamInterface $stream
66
     */
67
    private function __construct(StreamInterface $stream)
68
    {
69
        $this->stream = $stream;
70
    }
71
72
    /**
73
     * Creates an uploaded file from PHP's $_FILE upload data
74
     *
75
     * @param array{tmp_name: string, size: int, error: int, name: ?string, type: ?string} $fileUploadData
76
     *
77
     * @return UploadedFile
78
     */
79
    public static function create(array $fileUploadData): UploadedFile
80
    {
81
        $uploadedFile = new UploadedFile(new FileStream($fileUploadData['tmp_name']));
82
        $uploadedFile->size = (int) $fileUploadData['size'];
83
        $uploadedFile->error = $fileUploadData['error'];
84
        $uploadedFile->clientName = $fileUploadData['name'];
85
        $uploadedFile->mediaType = $fileUploadData['type'];
86
        $uploadedFile->tmpFile = $fileUploadData['tmp_name'];
87
88
        return $uploadedFile;
89
    }
90
91
    /**
92
     * Retrieve a stream representing the uploaded file.
93
     *
94
     * @return StreamInterface Stream representation of the uploaded file.
95
     *
96
     * @throws RuntimeException in cases when no stream is available
97
     */
98
    public function getStream(): StreamInterface
99
    {
100
        if (!$this->stream) {
101
            throw new RuntimeException(
102
                "The uploaded file stream is no longer available."
103
            );
104
        }
105
        return $this->stream;
106
    }
107
108
    /**
109
     * Retrieve the file size.
110
     *
111
     * @return int The file size in bytes
112
     */
113
    public function getSize(): int
114
    {
115
        return $this->size;
116
    }
117
118
    /**
119
     * Retrieve the error associated with the uploaded file.
120
     *
121
     * If the file was uploaded successfully, this method will return
122
     * UPLOAD_ERR_OK.
123
     *
124
     * @see http://php.net/manual/en/features.file-upload.errors.php
125
     *
126
     * @return int One of PHP's UPLOAD_ERR_XXX constants.
127
     */
128
    public function getError(): int
129
    {
130
        return $this->error;
131
    }
132
133
    /**
134
     * Retrieve the filename sent by the client.
135
     *
136
     * Do not trust the value returned by this method. A client could send
137
     * a malicious filename with the intention to corrupt or hack your
138
     * application.
139
     *
140
     * @return null|string The filename sent by the client or null if none
141
     *     was provided.
142
     */
143
    public function getClientFilename(): ?string
144
    {
145
        return $this->clientName;
146
    }
147
148
    /**
149
     * Retrieve the media type sent by the client.
150
     *
151
     * Do not trust the value returned by this method. A client could send
152
     * a malicious media type with the intention to corrupt or hack your
153
     * application.
154
     *
155
     * @return null|string The media type sent by the client or null if none
156
     *     was provided.
157
     */
158
    public function getClientMediaType(): ?string
159
    {
160
        return $this->mediaType;
161
    }
162
163
    /**
164
     * Move the uploaded file to a new location.
165
     *
166
     * $targetPath may be an absolute path, or a relative path. If it is a
167
     * relative path, resolution should be the same as used by PHP's rename()
168
     * function.
169
     *
170
     * The original file or stream will be removed on completion.
171
     *
172
     * If this method is called more than once, any subsequent calls will raise
173
     * an exception.
174
     *
175
     * @see http://php.net/is_uploaded_file
176
     * @see http://php.net/move_uploaded_file
177
     *
178
     * @param string $targetPath Path to which to move the uploaded file.
179
     *
180
     * @throws InvalidArgumentException if the $targetPath specified is invalid.
181
     * @throws RuntimeException on any error during the move operation, or on
182
     *     the second or subsequent call to the method.
183
     */
184
    public function moveTo(string $targetPath): void
185
    {
186
        $this->checkMoved();
187
        $this->checkTargetDirExists($targetPath);
188
        $this->checkUpload();
189
190
        $exception = null;
191
        set_error_handler(function (int $number, string $error) use (&$exception): bool {
192
            $exception = new RuntimeException(
193
                "Cannot move uploaded file: ($number) $error"
194
            );
195
            return true;
196
        });
197
198
        move_uploaded_file($this->tmpFile, $targetPath);
199
        restore_error_handler();
200
201
        if ($exception instanceof RuntimeException) {
202
            throw $exception;
203
        }
204
205
        $this->stream = null;
206
        $this->moved = true;
207
    }
208
209
    /**
210
     * Check if the current file is already moved
211
     */
212
    private function checkMoved(): void
213
    {
214
        if ($this->moved) {
215
            throw new RuntimeException(
216
                "Uploaded file has already been move."
217
            );
218
        }
219
    }
220
221
    /**
222
     * Check if the target directory exists
223
     *
224
     * @param string $targetPath
225
     */
226
    private function checkTargetDirExists(string $targetPath): void
227
    {
228
        if (!is_dir(\dirname($targetPath))) {
229
            throw new InvalidArgumentException(
230
                "Cannot move uploaded file: target directory dos not exists."
231
            );
232
        }
233
    }
234
235
    /**
236
     * Checks if upload was successful
237
     */
238
    private function checkUpload(): void
239
    {
240
        if (!is_uploaded_file($this->tmpFile)) {
241
            throw new RuntimeException(
242
                "Cannot move uploaded file: the client file was not uploaded."
243
            );
244
        }
245
    }
246
}
247