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

UploadedFile::moveTo()   B

Complexity

Conditions 10
Paths 23

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 10.2918

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 21
c 1
b 0
f 0
nc 23
nop 1
dl 0
loc 37
ccs 18
cts 21
cp 0.8571
crap 10.2918
rs 7.6666

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
}