Completed
Push — master ( 38507d...f4a4ac )
by Tobias
03:05
created

UploadedFile::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 5
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\StreamInterface;
9
use Psr\Http\Message\UploadedFileInterface;
10
use RuntimeException;
11
12
/**
13
 * @author Michael Dowling and contributors to guzzlehttp/psr7
14
 * @author Tobias Nyholm <[email protected]>
15
 */
16
final class UploadedFile implements UploadedFileInterface
17
{
18
    /** @var int[] */
19
    private static $errors = [
20
        UPLOAD_ERR_OK, UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE,
21
        UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION,
22
    ];
23
24
    /** @var string */
25
    private $clientFilename;
26
27
    /** @var string */
28
    private $clientMediaType;
29
30
    /** @var int */
31
    private $error;
32
33
    /** @var null|string */
34
    private $file;
35
36
    /** @var bool */
37
    private $moved = false;
38
39
    /** @var null|int */
40
    private $size;
41
42
    /** @var null|StreamInterface */
43
    private $stream;
44
45
    /**
46
     * @param StreamInterface|string|resource $streamOrFile
47
     * @param int                             $size
48
     * @param int                             $errorStatus
49
     * @param string|null                     $clientFilename
50
     * @param string|null                     $clientMediaType
51
     */
52 75
    public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
53
    {
54 75
        $this->setError($errorStatus);
55 66
        $this->setSize($size);
56 66
        $this->setClientFilename($clientFilename);
57 60
        $this->setClientMediaType($clientMediaType);
58
59 54
        if ($this->isOk()) {
60 32
            $this->setStreamOrFile($streamOrFile);
61
        }
62 47
    }
63
64
    /**
65
     * Depending on the value set file or stream variable.
66
     *
67
     * @param string|resource|StreamInterface $streamOrFile
68
     *
69
     * @throws InvalidArgumentException
70
     */
71 32
    private function setStreamOrFile($streamOrFile): void
72
    {
73 32
        if (is_string($streamOrFile)) {
74 1
            $this->file = $streamOrFile;
75 31
        } elseif (is_resource($streamOrFile)) {
76 1
            $this->stream = Stream::createFromResource($streamOrFile);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Nyholm\Psr7\Stream::cre...Resource($streamOrFile) of type object<self> is incompatible with the declared type null|object<Psr\Http\Message\StreamInterface> of property $stream.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
77 30
        } elseif ($streamOrFile instanceof StreamInterface) {
78 23
            $this->stream = $streamOrFile;
79
        } else {
80 7
            throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile');
81
        }
82 25
    }
83
84 75
    private function setError($error): void
85
    {
86 75
        if (false === is_int($error)) {
87 7
            throw new InvalidArgumentException('Upload file error status must be an integer');
88
        }
89
90 68
        if (false === in_array($error, self::$errors)) {
91 2
            throw new InvalidArgumentException('Invalid error status for UploadedFile');
92
        }
93
94 66
        $this->error = $error;
95 66
    }
96
97 66
    private function setSize($size): void
98
    {
99 66
        if (false === is_int($size)) {
100
            throw new InvalidArgumentException('Upload file size must be an integer');
101
        }
102
103 66
        $this->size = $size;
104 66
    }
105
106 66
    private function isStringOrNull($param): bool
107
    {
108 66
        return in_array(gettype($param), ['string', 'NULL']);
109
    }
110
111 16
    private function isStringNotEmpty($param): bool
112
    {
113 16
        return is_string($param) && false === empty($param);
114
    }
115
116 66
    private function setClientFilename($clientFilename): void
117
    {
118 66
        if (false === $this->isStringOrNull($clientFilename)) {
119 6
            throw new InvalidArgumentException('Upload file client filename must be a string or null');
120
        }
121
122 60
        $this->clientFilename = $clientFilename;
123 60
    }
124
125 60
    private function setClientMediaType($clientMediaType): void
126
    {
127 60
        if (false === $this->isStringOrNull($clientMediaType)) {
128 6
            throw new InvalidArgumentException('Upload file client media type must be a string or null');
129
        }
130
131 54
        $this->clientMediaType = $clientMediaType;
132 54
    }
133
134
    /**
135
     * @return bool Return true if there is no upload error.
136
     */
137 54
    private function isOk(): bool
138
    {
139 54
        return UPLOAD_ERR_OK === $this->error;
140
    }
141
142
    /**
143
     * @throws RuntimeException if is moved or not ok
144
     */
145 34
    private function validateActive(): void
146
    {
147 34
        if (false === $this->isOk()) {
148 14
            throw new RuntimeException('Cannot retrieve stream due to upload error');
149
        }
150
151 20
        if ($this->moved) {
152 4
            throw new RuntimeException('Cannot retrieve stream after it has already been moved');
153
        }
154 20
    }
155
156 18
    public function getStream(): StreamInterface
157
    {
158 18
        $this->validateActive();
159
160 11
        if ($this->stream instanceof StreamInterface) {
161 11
            return $this->stream;
162
        }
163
164
        $resource = fopen($this->file, 'r');
165
166
        return Stream::createFromResource($resource);
167
    }
168
169 23
    public function moveTo($targetPath): void
170
    {
171 23
        $this->validateActive();
172
173 16
        if (false === $this->isStringNotEmpty($targetPath)) {
174 8
            throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
175
        }
176
177 8
        if (null !== $this->file) {
178 1
            $this->moved = 'cli' === php_sapi_name()
179 1
                ? rename($this->file, $targetPath)
180 1
                : move_uploaded_file($this->file, $targetPath);
181
        } else {
182 7
            $stream = $this->getStream();
183 7
            if ($stream->isSeekable()) {
184 7
                $stream->rewind();
185
            }
186 7
            $this->copyToStream($stream, Stream::createFromResource(fopen($targetPath, 'w')));
187 7
            $this->moved = true;
188
        }
189
190 8
        if (false === $this->moved) {
191
            throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath));
192
        }
193 8
    }
194
195 3
    public function getSize(): ?int
196
    {
197 3
        return $this->size;
198
    }
199
200 10
    public function getError(): int
201
    {
202 10
        return $this->error;
203
    }
204
205 3
    public function getClientFilename(): ?string
206
    {
207 3
        return $this->clientFilename;
208
    }
209
210 3
    public function getClientMediaType(): ?string
211
    {
212 3
        return $this->clientMediaType;
213
    }
214
215
    /**
216
     * Copy the contents of a stream into another stream until the given number
217
     * of bytes have been read.
218
     *
219
     * @author Michael Dowling and contributors to guzzlehttp/psr7
220
     *
221
     * @param StreamInterface $source Stream to read from
222
     * @param StreamInterface $dest   Stream to write to
223
     * @param int             $maxLen Maximum number of bytes to read. Pass -1
224
     *                                to read the entire stream
225
     *
226
     * @throws \RuntimeException on error
227
     */
228 7
    private function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)
229
    {
230 7
        if ($maxLen === -1) {
231 7
            while (!$source->eof()) {
232 7
                if (!$dest->write($source->read(1048576))) {
233 7
                    break;
234
                }
235
            }
236
237 7
            return;
238
        }
239
240
        $bytes = 0;
241
        while (!$source->eof()) {
242
            $buf = $source->read($maxLen - $bytes);
243
            if (!($len = strlen($buf))) {
244
                break;
245
            }
246
            $bytes += $len;
247
            $dest->write($buf);
248
            if ($bytes === $maxLen) {
249
                break;
250
            }
251
        }
252
    }
253
}
254