Completed
Push — master ( 905e92...9a896f )
by Tobias
02:28
created

UploadedFile::setError()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Nyholm\Psr7;
6
7
use Psr\Http\Message\{StreamInterface, UploadedFileInterface};
8
9
/**
10
 * @author Michael Dowling and contributors to guzzlehttp/psr7
11
 * @author Tobias Nyholm <[email protected]>
12
 * @author Martijn van der Ven <[email protected]>
13
 */
14
final class UploadedFile implements UploadedFileInterface
15
{
16
    /** @var int[] */
17
    private static $errors = [
18
        UPLOAD_ERR_OK, UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE,
19
        UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION,
20
    ];
21
22
    /** @var string */
23
    private $clientFilename;
24
25
    /** @var string */
26
    private $clientMediaType;
27
28
    /** @var int */
29
    private $error;
30
31
    /** @var null|string */
32
    private $file;
33
34
    /** @var bool */
35
    private $moved = false;
36
37
    /** @var null|int */
38
    private $size;
39
40
    /** @var null|StreamInterface */
41
    private $stream;
42
43
    /**
44
     * @param StreamInterface|string|resource $streamOrFile
45
     * @param int                             $size
46
     * @param int                             $errorStatus
47
     * @param string|null                     $clientFilename
48
     * @param string|null                     $clientMediaType
49
     */
50 75
    public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
51
    {
52 75
        $this->setError($errorStatus);
53 66
        $this->setSize($size);
54 66
        $this->setClientFilename($clientFilename);
55 60
        $this->setClientMediaType($clientMediaType);
56
57 54
        if ($this->isOk()) {
58 32
            $this->setStreamOrFile($streamOrFile);
59
        }
60 47
    }
61
62
    /**
63
     * Depending on the value set file or stream variable.
64
     *
65
     * @param string|resource|StreamInterface $streamOrFile
66
     *
67
     * @throws \InvalidArgumentException
68
     */
69 32
    private function setStreamOrFile($streamOrFile): void
70
    {
71 32
        if (is_string($streamOrFile)) {
72 1
            $this->file = $streamOrFile;
73 31
        } elseif (is_resource($streamOrFile)) {
74 1
            $this->stream = Stream::create($streamOrFile);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Nyholm\Psr7\Stream::create($streamOrFile) of type object<Psr\Http\Message\StreamInterface> is incompatible with the declared type null|object<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...
75 30
        } elseif ($streamOrFile instanceof StreamInterface) {
76 23
            $this->stream = $streamOrFile;
77
        } else {
78 7
            throw new \InvalidArgumentException('Invalid stream or file provided for UploadedFile');
79
        }
80 25
    }
81
82 75
    private function setError($error): void
83
    {
84 75
        if (false === is_int($error)) {
85 7
            throw new \InvalidArgumentException('Upload file error status must be an integer');
86
        }
87
88 68
        if (false === in_array($error, self::$errors)) {
89 2
            throw new \InvalidArgumentException('Invalid error status for UploadedFile');
90
        }
91
92 66
        $this->error = $error;
93 66
    }
94
95 66
    private function setSize($size): void
96
    {
97 66
        if (false === is_int($size)) {
98
            throw new \InvalidArgumentException('Upload file size must be an integer');
99
        }
100
101 66
        $this->size = $size;
102 66
    }
103
104 66
    private function isStringOrNull($param): bool
105
    {
106 66
        return in_array(gettype($param), ['string', 'NULL']);
107
    }
108
109 16
    private function isStringNotEmpty($param): bool
110
    {
111 16
        return is_string($param) && false === empty($param);
112
    }
113
114 66
    private function setClientFilename($clientFilename): void
115
    {
116 66
        if (false === $this->isStringOrNull($clientFilename)) {
117 6
            throw new \InvalidArgumentException('Upload file client filename must be a string or null');
118
        }
119
120 60
        $this->clientFilename = $clientFilename;
121 60
    }
122
123 60
    private function setClientMediaType($clientMediaType): void
124
    {
125 60
        if (false === $this->isStringOrNull($clientMediaType)) {
126 6
            throw new \InvalidArgumentException('Upload file client media type must be a string or null');
127
        }
128
129 54
        $this->clientMediaType = $clientMediaType;
130 54
    }
131
132
    /**
133
     * @return bool Return true if there is no upload error.
134
     */
135 54
    private function isOk(): bool
136
    {
137 54
        return UPLOAD_ERR_OK === $this->error;
138
    }
139
140
    /**
141
     * @throws \RuntimeException if is moved or not ok
142
     */
143 34
    private function validateActive(): void
144
    {
145 34
        if (false === $this->isOk()) {
146 14
            throw new \RuntimeException('Cannot retrieve stream due to upload error');
147
        }
148
149 20
        if ($this->moved) {
150 4
            throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
151
        }
152 20
    }
153
154 18
    public function getStream(): StreamInterface
155
    {
156 18
        $this->validateActive();
157
158 11
        if ($this->stream instanceof StreamInterface) {
159 11
            return $this->stream;
160
        }
161
162
        $resource = fopen($this->file, 'r');
163
164
        return Stream::create($resource);
165
    }
166
167 23
    public function moveTo($targetPath): void
168
    {
169 23
        $this->validateActive();
170
171 16
        if (false === $this->isStringNotEmpty($targetPath)) {
172 8
            throw new \InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
173
        }
174
175 8
        if (null !== $this->file) {
176 1
            $this->moved = 'cli' === php_sapi_name() ? rename($this->file, $targetPath) : move_uploaded_file($this->file, $targetPath);
177
        } else {
178 7
            $stream = $this->getStream();
179 7
            if ($stream->isSeekable()) {
180 7
                $stream->rewind();
181
            }
182 7
            $this->copyToStream($stream, Stream::create(fopen($targetPath, 'w')));
183 7
            $this->moved = true;
184
        }
185
186 8
        if (false === $this->moved) {
187
            throw new \RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath));
188
        }
189 8
    }
190
191 3
    public function getSize(): ?int
192
    {
193 3
        return $this->size;
194
    }
195
196 10
    public function getError(): int
197
    {
198 10
        return $this->error;
199
    }
200
201 3
    public function getClientFilename(): ?string
202
    {
203 3
        return $this->clientFilename;
204
    }
205
206 3
    public function getClientMediaType(): ?string
207
    {
208 3
        return $this->clientMediaType;
209
    }
210
211
    /**
212
     * Copy the contents of a stream into another stream until the given number
213
     * of bytes have been read.
214
     *
215
     * @author Michael Dowling and contributors to guzzlehttp/psr7
216
     *
217
     * @param StreamInterface $source Stream to read from
218
     * @param StreamInterface $dest   Stream to write to
219
     * @param int             $maxLen Maximum number of bytes to read. Pass -1
220
     *                                to read the entire stream
221
     *
222
     * @throws \RuntimeException on error
223
     */
224 7
    private function copyToStream(StreamInterface $source, StreamInterface $dest, $maxLen = -1)
225
    {
226 7
        if ($maxLen === -1) {
227 7
            while (!$source->eof()) {
228 7
                if (!$dest->write($source->read(1048576))) {
229 7
                    break;
230
                }
231
            }
232
233 7
            return;
234
        }
235
236
        $bytes = 0;
237
        while (!$source->eof()) {
238
            $buf = $source->read($maxLen - $bytes);
239
            if (!($len = strlen($buf))) {
240
                break;
241
            }
242
            $bytes += $len;
243
            $dest->write($buf);
244
            if ($bytes === $maxLen) {
245
                break;
246
            }
247
        }
248
    }
249
}
250