Completed
Push — master ( e78b35...c448e0 )
by Tobias
18:18 queued 08:25
created

UploadedFile::isStringOrNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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