Test Failed
Push — master ( 0b6467...e6142c )
by Alexpts
16:38
created

UploadedFile::__construct()   C

Complexity

Conditions 12
Paths 9

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 21
c 1
b 0
f 0
nc 9
nop 5
dl 0
loc 33
ccs 21
cts 21
cp 1
crap 12
rs 6.9666

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
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_string;
14
use function move_uploaded_file;
15
use function rename;
16
use function sprintf;
17
18
final class UploadedFile implements UploadedFileInterface
19
{
20
    /** @var array */
21
    private const ERRORS = [
22
        \UPLOAD_ERR_OK => 1,
23
        \UPLOAD_ERR_INI_SIZE => 1,
24
        \UPLOAD_ERR_FORM_SIZE => 1,
25
        \UPLOAD_ERR_PARTIAL => 1,
26
        \UPLOAD_ERR_NO_FILE => 1,
27
        \UPLOAD_ERR_NO_TMP_DIR => 1,
28
        \UPLOAD_ERR_CANT_WRITE => 1,
29
        \UPLOAD_ERR_EXTENSION => 1,
30
    ];
31
32
    protected ?string $clientFilename = null;
33
    protected ?string $clientMediaType = null;
34
    protected int $error;
35
    private ?string $file = null;
36
    private bool $moved = false;
37
    private int $size;
38
    private ?StreamInterface $stream = null;
39
40
    /**
41
     * @param StreamInterface|string|resource $streamOrFile
42
     * @param int $size
43
     * @param int $errorStatus
44
     * @param string|null $clientFilename
45
     * @param string|null $clientMediaType
46
     */
47 68
    public function __construct($streamOrFile, $size, $errorStatus, $clientFilename = null, $clientMediaType = null)
48
    {
49 68
        if (false === is_int($errorStatus) || !isset(self::ERRORS[$errorStatus])) {
50 9
            throw new InvalidArgumentException('Upload file error status must be an integer value and one of the "UPLOAD_ERR_*" constants.');
51
        }
52
53 59
        if (false === is_int($size)) {
0 ignored issues
show
introduced by
The condition false === is_int($size) is always false.
Loading history...
54 1
            throw new InvalidArgumentException('Upload file size must be an integer');
55
        }
56
57 58
        if (null !== $clientFilename && !is_string($clientFilename)) {
0 ignored issues
show
introduced by
The condition is_string($clientFilename) is always true.
Loading history...
58 6
            throw new InvalidArgumentException('Upload file client filename must be a string or null');
59
        }
60
61 52
        if (null !== $clientMediaType && !is_string($clientMediaType)) {
0 ignored issues
show
introduced by
The condition is_string($clientMediaType) is always true.
Loading history...
62 6
            throw new InvalidArgumentException('Upload file client media type must be a string or null');
63
        }
64
65 46
        $this->error = $errorStatus;
66 46
        $this->size = $size;
67 46
        $this->clientFilename = $clientFilename;
68 46
        $this->clientMediaType = $clientMediaType;
69
70 46
        if (\UPLOAD_ERR_OK === $this->error) {
71
            // Depending on the value set file or stream variable.
72 25
            if (is_string($streamOrFile)) {
73 3
                $this->file = $streamOrFile;
74 22
            } elseif (\is_resource($streamOrFile)) {
75 1
                $this->stream = Stream::create($streamOrFile);
76 21
            } elseif ($streamOrFile instanceof StreamInterface) {
0 ignored issues
show
introduced by
$streamOrFile is always a sub-type of Psr\Http\Message\StreamInterface.
Loading history...
77 14
                $this->stream = $streamOrFile;
78
            } else {
79 7
                throw new InvalidArgumentException('Invalid stream or file provided for UploadedFile');
80
            }
81
        }
82 39
    }
83
84
    /**
85
     * @throws RuntimeException if is moved or not ok
86
     */
87 30
    private function validateActive(): void
88
    {
89 30
        if (\UPLOAD_ERR_OK !== $this->error) {
90 14
            throw new RuntimeException('Cannot retrieve stream due to upload error');
91
        }
92
93 16
        if ($this->moved) {
94 2
            throw new RuntimeException('Cannot retrieve stream after it has already been moved');
95
        }
96 16
    }
97
98 14
    public function getStream(): StreamInterface
99
    {
100 14
        $this->validateActive();
101
102 7
        if ($this->stream instanceof StreamInterface) {
103 6
            return $this->stream;
104
        }
105
106 1
        $resource = 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

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