UploadedFile::getError()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace PTS\Psr7;
5
6
use InvalidArgumentException;
7
use Psr\Http\Message\StreamInterface;
8
use Psr\Http\Message\UploadedFileInterface;
9
use RuntimeException;
10
use Throwable;
11
use function fopen;
12
use function is_int;
13
use function is_resource;
14
use function is_string;
15
use function move_uploaded_file;
16
use function rename;
17
use function sprintf;
18
19
final class UploadedFile implements UploadedFileInterface
20
{
21
    /** @var array */
22
    private const ERRORS = [
23
        \UPLOAD_ERR_OK => 1,
24
        \UPLOAD_ERR_INI_SIZE => 1,
25
        \UPLOAD_ERR_FORM_SIZE => 1,
26
        \UPLOAD_ERR_PARTIAL => 1,
27
        \UPLOAD_ERR_NO_FILE => 1,
28
        \UPLOAD_ERR_NO_TMP_DIR => 1,
29
        \UPLOAD_ERR_CANT_WRITE => 1,
30
        \UPLOAD_ERR_EXTENSION => 1,
31
    ];
32
33
    protected ?string $clientFilename = null;
34
    protected ?string $clientMediaType = null;
35
    protected int $error;
36
    private ?string $file = null;
37
    private bool $moved = false;
38
    private int $size;
39
    private ?StreamInterface $stream = null;
40
41
    /**
42
     * @param StreamInterface|string|resource $streamOrFile
43
     * @param int $size
44
     * @param int $errorStatus
45
     * @param string|null $clientFilename
46
     * @param string|null $clientMediaType
47 68
     */
48
    public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
49 68
    {
50 9
        if (false === is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
51
            throw new InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants.');
52
        }
53 59
54 1
        if (false === is_int($size)) {
0 ignored issues
show
introduced by
The condition false === is_int($size) is always false.
Loading history...
55
            throw new InvalidArgumentException('Upload file size must be an integer');
56
        }
57 58
58 6
        if (null !== $clientFilename && !is_string($clientFilename)) {
0 ignored issues
show
introduced by
The condition is_string($clientFilename) is always true.
Loading history...
59
            throw new InvalidArgumentException('Upload file client filename must be a string or null');
60
        }
61 52
62 6
        if (null !== $clientMediaType && !is_string($clientMediaType)) {
0 ignored issues
show
introduced by
The condition is_string($clientMediaType) is always true.
Loading history...
63
            throw new InvalidArgumentException('Upload file client media type must be a string or null');
64
        }
65 46
66 46
        $this->error = $errorStatus;
67 46
        $this->size = $size;
68 46
        $this->clientFilename = $clientFilename;
69
        $this->clientMediaType = $clientMediaType;
70 46
71
        if (\UPLOAD_ERR_OK === $this->error) {
72 25
            // Depending on the value set file or stream variable.
73 3
            if (is_string($streamOrFile) && '' !== $streamOrFile) {
74 22
                $this->file = $streamOrFile;
75 1
            } elseif (is_resource($streamOrFile)) {
76 21
                $this->stream = Stream::create($streamOrFile);
77 14
            } elseif ($streamOrFile instanceof StreamInterface) {
78
                $this->stream = $streamOrFile;
79 7
            } else {
80
                throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile');
81
            }
82 39
        }
83
    }
84
85
    /**
86
     * @throws RuntimeException if is moved or not ok
87 30
     */
88
    private function validateActive(): void
89 30
    {
90 14
        if (\UPLOAD_ERR_OK !== $this->error) {
91
            throw new RuntimeException('Cannot retrieve stream due to upload error');
92
        }
93 16
94 2
        if ($this->moved) {
95
            throw new RuntimeException('Cannot retrieve stream after it has already been moved');
96 16
        }
97
    }
98 14
99
    public function getStream(): StreamInterface
100 14
    {
101
        $this->validateActive();
102 7
103 6
        if ($this->stream instanceof StreamInterface) {
104
            return $this->stream;
105
        }
106 1
107
        try {
108 1
            return Stream::create(fopen($this->file, 'r'));
0 ignored issues
show
Bug introduced by
It seems like $this->file can also be of type null; however, parameter $filename of fopen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

108
            return Stream::create(fopen(/** @scrutinizer ignore-type */ $this->file, 'r'));
Loading history...
109
        } catch (Throwable $e) {
110
            throw new RuntimeException(sprintf('The file "%s" cannot be opened.', $this->file));
111 20
        }
112
    }
113 20
114
    public function moveTo($targetPath): void
115 13
    {
116 8
        $this->validateActive();
117
118
        if (!is_string($targetPath) || '' === $targetPath) {
119 5
            throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
120 1
        }
121 1
122
        if (null !== $this->file) {
123
            $this->moved = 'cli' === \PHP_SAPI
124 1
                ? rename($this->file, $targetPath)
125
                : move_uploaded_file($this->file, $targetPath);
126
127
            if (false === $this->moved) {
128 1
                throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath));
129
            }
130
131 4
            return;
132 4
        }
133 4
134
        $stream = $this->getStream();
135
        if ($stream->isSeekable()) {
136
            $stream->rewind();
137
        }
138 4
139 3
        // Copy the contents of a stream into another stream until end-of-file.
140 3
        try {
141
            $dest = Stream::create(fopen($targetPath, 'w'));
142
            while (!$stream->eof()) {
143
                if (!$dest->write($stream->read(1048576))) {
144
                    break;
145 3
                }
146 1
            }
147 1
148
            $this->moved = true;
149 3
        } catch (Throwable $throwable) {
150
            throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath), 0, $throwable);
151 2
        }
152
    }
153 2
154
    public function getSize(): int
155
    {
156 7
        return $this->size;
157
    }
158 7
159
    public function getError(): int
160
    {
161 1
        return $this->error;
162
    }
163 1
164
    public function getClientFilename(): ?string
165
    {
166 1
        return $this->clientFilename;
167
    }
168 1
169
    public function getClientMediaType(): ?string
170 1
    {
171
        return $this->clientMediaType;
172
    }
173
}