Passed
Push — master ( 90372d...e80252 )
by Nikolay
25:24
created

UploadedFile::moveTo()   B

Complexity

Conditions 11
Paths 7

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 21
dl 0
loc 38
c 0
b 0
f 0
rs 7.3166
cc 11
nc 7
nop 1

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
2
/**
3
 * @see       https://github.com/zendframework/zend-diactoros for the canonical source repository
4
 * @copyright Copyright (c) 2015-2018 Zend Technologies USA Inc. (http://www.zend.com)
5
 * @license   https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6
 */
7
8
declare(strict_types=1);
9
10
namespace Zend\Diactoros;
11
12
use Psr\Http\Message\StreamInterface;
13
use Psr\Http\Message\UploadedFileInterface;
14
15
use function dirname;
16
use function fclose;
17
use function fopen;
18
use function fwrite;
19
use function is_dir;
20
use function is_int;
21
use function is_resource;
22
use function is_string;
23
use function is_writable;
24
use function move_uploaded_file;
25
use function sprintf;
26
use function strpos;
27
28
use const PHP_SAPI;
29
use const UPLOAD_ERR_CANT_WRITE;
30
use const UPLOAD_ERR_EXTENSION;
31
use const UPLOAD_ERR_FORM_SIZE;
32
use const UPLOAD_ERR_INI_SIZE;
33
use const UPLOAD_ERR_NO_FILE;
34
use const UPLOAD_ERR_NO_TMP_DIR;
35
use const UPLOAD_ERR_OK;
36
use const UPLOAD_ERR_PARTIAL;
37
38
class UploadedFile implements UploadedFileInterface
39
{
40
    const ERROR_MESSAGES = [
41
        UPLOAD_ERR_OK         => 'There is no error, the file uploaded with success',
42
        UPLOAD_ERR_INI_SIZE   => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
43
        UPLOAD_ERR_FORM_SIZE  => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was '
44
            . 'specified in the HTML form',
45
        UPLOAD_ERR_PARTIAL    => 'The uploaded file was only partially uploaded',
46
        UPLOAD_ERR_NO_FILE    => 'No file was uploaded',
47
        UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder',
48
        UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
49
        UPLOAD_ERR_EXTENSION  => 'A PHP extension stopped the file upload.',
50
    ];
51
52
    /**
53
     * @var string|null
54
     */
55
    private $clientFilename;
56
57
    /**
58
     * @var string|null
59
     */
60
    private $clientMediaType;
61
62
    /**
63
     * @var int
64
     */
65
    private $error;
66
67
    /**
68
     * @var null|string
69
     */
70
    private $file;
71
72
    /**
73
     * @var bool
74
     */
75
    private $moved = false;
76
77
    /**
78
     * @var int
79
     */
80
    private $size;
81
82
    /**
83
     * @var null|StreamInterface
84
     */
85
    private $stream;
86
87
    /**
88
     * @param string|resource $streamOrFile
89
     * @param int $size
90
     * @param int $errorStatus
91
     * @param string|null $clientFilename
92
     * @param string|null $clientMediaType
93
     * @throws Exception\InvalidArgumentException
94
     */
95
    public function __construct(
96
        $streamOrFile,
97
        int $size,
98
        int $errorStatus,
99
        string $clientFilename = null,
100
        string $clientMediaType = null
101
    ) {
102
        if ($errorStatus === UPLOAD_ERR_OK) {
103
            if (is_string($streamOrFile)) {
104
                $this->file = $streamOrFile;
105
            }
106
            if (is_resource($streamOrFile)) {
107
                $this->stream = new Stream($streamOrFile);
108
            }
109
110
            if (! $this->file && ! $this->stream) {
111
                if (! $streamOrFile instanceof StreamInterface) {
0 ignored issues
show
introduced by
$streamOrFile is never a sub-type of Psr\Http\Message\StreamInterface.
Loading history...
112
                    throw new Exception\InvalidArgumentException('Invalid stream or file provided for UploadedFile');
113
                }
114
                $this->stream = $streamOrFile;
115
            }
116
        }
117
118
        $this->size = $size;
119
120
        if (0 > $errorStatus || 8 < $errorStatus) {
121
            throw new Exception\InvalidArgumentException(
122
                'Invalid error status for UploadedFile; must be an UPLOAD_ERR_* constant'
123
            );
124
        }
125
        $this->error = $errorStatus;
126
127
        $this->clientFilename = $clientFilename;
128
        $this->clientMediaType = $clientMediaType;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     * @throws Exception\UploadedFileAlreadyMovedException if the upload was
134
     *     not successful.
135
     */
136
    public function getStream() : StreamInterface
137
    {
138
        if ($this->error !== UPLOAD_ERR_OK) {
139
            throw Exception\UploadedFileErrorException::dueToStreamUploadError(
140
                self::ERROR_MESSAGES[$this->error]
141
            );
142
        }
143
144
        if ($this->moved) {
145
            throw new Exception\UploadedFileAlreadyMovedException();
146
        }
147
148
        if ($this->stream instanceof StreamInterface) {
149
            return $this->stream;
150
        }
151
152
        $this->stream = new Stream($this->file);
153
        return $this->stream;
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     *
159
     * @see http://php.net/is_uploaded_file
160
     * @see http://php.net/move_uploaded_file
161
     * @param string $targetPath Path to which to move the uploaded file.
162
     * @throws Exception\UploadedFileErrorException if the upload was not successful.
163
     * @throws Exception\InvalidArgumentException if the $path specified is invalid.
164
     * @throws Exception\UploadedFileErrorException on any error during the
165
     *     move operation, or on the second or subsequent call to the method.
166
     */
167
    public function moveTo($targetPath) : void
168
    {
169
        if ($this->moved) {
170
            throw new Exception\UploadedFileAlreadyMovedException('Cannot move file; already moved!');
171
        }
172
173
        if ($this->error !== UPLOAD_ERR_OK) {
174
            throw Exception\UploadedFileErrorException::dueToStreamUploadError(
175
                self::ERROR_MESSAGES[$this->error]
176
            );
177
        }
178
179
        if (! is_string($targetPath) || empty($targetPath)) {
0 ignored issues
show
introduced by
The condition is_string($targetPath) is always true.
Loading history...
180
            throw new Exception\InvalidArgumentException(
181
                'Invalid path provided for move operation; must be a non-empty string'
182
            );
183
        }
184
185
        $targetDirectory = dirname($targetPath);
186
        if (! is_dir($targetDirectory) || ! is_writable($targetDirectory)) {
187
            throw Exception\UploadedFileErrorException::dueToUnwritableTarget($targetDirectory);
188
        }
189
190
        $sapi = PHP_SAPI;
191
        switch (true) {
192
            case (empty($sapi) || 0 === strpos($sapi, 'cli') || ! $this->file):
193
                // Non-SAPI environment, or no filename present
194
                $this->writeFile($targetPath);
195
                break;
196
            default:
197
                // SAPI environment, with file present
198
                if (false === move_uploaded_file($this->file, $targetPath)) {
199
                    throw Exception\UploadedFileErrorException::forUnmovableFile();
200
                }
201
                break;
202
        }
203
204
        $this->moved = true;
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     *
210
     * @return int|null The file size in bytes or null if unknown.
211
     */
212
    public function getSize() : ?int
213
    {
214
        return $this->size;
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     *
220
     * @see http://php.net/manual/en/features.file-upload.errors.php
221
     * @return int One of PHP's UPLOAD_ERR_XXX constants.
222
     */
223
    public function getError() : int
224
    {
225
        return $this->error;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     *
231
     * @return string|null The filename sent by the client or null if none
232
     *     was provided.
233
     */
234
    public function getClientFilename() : ?string
235
    {
236
        return $this->clientFilename;
237
    }
238
239
    /**
240
     * {@inheritdoc}
241
     */
242
    public function getClientMediaType() : ?string
243
    {
244
        return $this->clientMediaType;
245
    }
246
247
    /**
248
     * Write internal stream to given path
249
     *
250
     * @param string $path
251
     */
252
    private function writeFile(string $path) : void
253
    {
254
        $handle = fopen($path, 'wb+');
255
        if (false === $handle) {
256
            throw Exception\UploadedFileErrorException::dueToUnwritablePath();
257
        }
258
259
        $stream = $this->getStream();
260
        $stream->rewind();
261
        while (! $stream->eof()) {
262
            fwrite($handle, $stream->read(4096));
263
        }
264
265
        fclose($handle);
266
    }
267
}
268