Completed
Push — master ( 78d4e7...979ecb )
by smiley
02:57
created

UploadedFile::moveTo()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 8
nop 1
dl 0
loc 27
rs 8.5546
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class UploadedFile
4
 *
5
 * @filesource   UploadedFile.php
6
 * @created      11.08.2018
7
 * @package      chillerlan\HTTP\Psr7
8
 * @author       smiley <[email protected]>
9
 * @copyright    2018 smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\HTTP\Psr7;
14
15
use chillerlan\HTTP\Psr17;
16
use chillerlan\HTTP\Psr17\StreamFactory;
17
use InvalidArgumentException;
18
use Psr\Http\Message\{StreamInterface, UploadedFileInterface};
19
use RuntimeException;
20
21
final class UploadedFile implements UploadedFileInterface{
22
23
	/**
24
	 * @var int[]
25
	 */
26
	public const UPLOAD_ERRORS = [
27
		UPLOAD_ERR_OK,
28
		UPLOAD_ERR_INI_SIZE,
29
		UPLOAD_ERR_FORM_SIZE,
30
		UPLOAD_ERR_PARTIAL,
31
		UPLOAD_ERR_NO_FILE,
32
		UPLOAD_ERR_NO_TMP_DIR,
33
		UPLOAD_ERR_CANT_WRITE,
34
		UPLOAD_ERR_EXTENSION,
35
	];
36
37
	/**
38
	 * @var int
39
	 */
40
	private $error;
41
42
	/**
43
	 * @var int
44
	 */
45
	private $size;
46
47
	/**
48
	 * @var null|string
49
	 */
50
	private $clientFilename;
51
52
	/**
53
	 * @var null|string
54
	 */
55
	private $clientMediaType;
56
57
	/**
58
	 * @var null|string
59
	 */
60
	private $file;
61
62
	/**
63
	 * @var null|\Psr\Http\Message\StreamInterface
64
	 */
65
	private $stream;
66
67
	/**
68
	 * @var bool
69
	 */
70
	private $moved = false;
71
72
	/**
73
	 * @var \chillerlan\HTTP\Psr17\StreamFactory
74
	 */
75
	protected $streamFactory;
76
77
	/**
78
	 * @param \Psr\Http\Message\StreamInterface|string|resource $file
79
	 * @param int                                               $size
80
	 * @param int                                               $error
81
	 * @param string|null                                       $filename
82
	 * @param string|null                                       $mediaType
83
	 *
84
	 * @throws \InvalidArgumentException
85
	 */
86
	public function __construct($file, int $size, int $error, string $filename = null, string $mediaType = null){
87
88
		if(!in_array($error, $this::UPLOAD_ERRORS, true)){
89
			throw new InvalidArgumentException('Invalid error status for UploadedFile');
90
		}
91
92
		$this->size            = (int)$size; // int type hint also accepts float...
93
		$this->error           = $error;
94
		$this->clientFilename  = $filename;
95
		$this->clientMediaType = $mediaType;
96
		$this->streamFactory   = new StreamFactory;
97
98
		if($this->error === UPLOAD_ERR_OK){
99
100
			if(is_string($file)){
101
				$this->file = $file;
102
			}
103
			else{
104
				$this->stream = Psr17\create_stream_from_input($file);
105
			}
106
107
		}
108
109
	}
110
111
	/**
112
	 * @inheritdoc
113
	 */
114
	public function getStream():StreamInterface{
115
116
		$this->validateActive();
117
118
		if($this->stream instanceof StreamInterface){
119
			return $this->stream;
120
		}
121
122
		return $this->streamFactory->createStreamFromFile($this->file, 'r+');
123
	}
124
125
	/**
126
	 * @inheritdoc
127
	 */
128
	public function moveTo($targetPath):void{
129
130
		$this->validateActive();
131
132
		if(is_string($targetPath) && empty($targetPath)){
133
			throw new InvalidArgumentException('Invalid path provided for move operation; must be a non-empty string');
134
		}
135
136
		if(!is_writable($targetPath)){
137
			throw new RuntimeException(sprintf('Directory %s is not writable', $targetPath));
138
		}
139
140
		if($this->file !== null){
141
			$this->moved = php_sapi_name() === 'cli'
142
				? rename($this->file, $targetPath)
143
				: move_uploaded_file($this->file, $targetPath);
144
		}
145
		else{
146
			$this->copyToStream($this->streamFactory->createStreamFromFile($targetPath, 'r+'));
147
			$this->moved = true;
148
		}
149
150
		if($this->moved === false){
151
			throw new RuntimeException(sprintf('Uploaded file could not be moved to %s', $targetPath));
152
		}
153
154
	}
155
156
	/**
157
	 * @inheritdoc
158
	 */
159
	public function getSize():?int{
160
		return $this->size;
161
	}
162
163
	/**
164
	 * @inheritdoc
165
	 */
166
	public function getError():int{
167
		return $this->error;
168
	}
169
170
	/**
171
	 * @inheritdoc
172
	 */
173
	public function getClientFilename():?string{
174
		return $this->clientFilename;
175
	}
176
177
	/**
178
	 * @inheritdoc
179
	 */
180
	public function getClientMediaType():?string{
181
		return $this->clientMediaType;
182
	}
183
184
	/**
185
	 * @return void
186
	 * @throws RuntimeException if is moved or not ok
187
	 */
188
	private function validateActive():void{
189
190
		if($this->error !== UPLOAD_ERR_OK){
191
			throw new RuntimeException('Cannot retrieve stream due to upload error');
192
		}
193
194
		if($this->moved){
195
			throw new RuntimeException('Cannot retrieve stream after it has already been moved');
196
		}
197
198
	}
199
200
	/**
201
	 * Copy the contents of a stream into another stream until the given number
202
	 * of bytes have been read.
203
	 *
204
	 * @author Michael Dowling and contributors to guzzlehttp/psr7
205
	 *
206
	 * @param StreamInterface $dest   Stream to write to
0 ignored issues
show
Documentation introduced by
Should the type for parameter $dest not be \Psr\Http\Message\StreamInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
207
	 *
208
	 * @throws \RuntimeException on error
209
	 */
210
	private function copyToStream(StreamInterface $dest){
211
		$source = $this->getStream();
212
213
		if($source->isSeekable()){
214
			$source->rewind();
215
		}
216
217
		while(!$source->eof()){
218
219
			if(!$dest->write($source->read(1048576))){
220
				break;
221
			}
222
223
		}
224
225
	}
226
227
}
228