[FEATURE] Introduce Request/Response based on PSR-7
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Http / ServerRequest.php
1 <?php
2 namespace TYPO3\CMS\Core\Http;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ServerRequestInterface;
18 use Psr\Http\Message\StreamInterface;
19 use Psr\Http\Message\UploadedFileInterface;
20
21 /**
22 * Represents a typical request incoming from the server to be processed
23 * by the TYPO3 Core. The original request is built from the ServerRequestFactory
24 * inside TYPO3's Bootstrap.
25 *
26 * Note that the PSR-7 standard works with immutable value objects, meaning that
27 * any modification to a Request object using the "with" methods will result
28 * in a new Request object.
29 *
30 * Highly inspired by https://github.com/phly/http/
31 *
32 * @internal Note that this is not public API yet.
33 */
34 class ServerRequest extends Request implements ServerRequestInterface {
35
36 /**
37 * @var array
38 */
39 protected $attributes;
40
41 /**
42 * @var array
43 */
44 protected $cookieParams;
45
46 /**
47 * @var array
48 */
49 protected $parsedBody;
50
51 /**
52 * @var array
53 */
54 protected $queryParams;
55
56 /**
57 * @var array
58 */
59 protected $serverParams;
60
61 /**
62 * @var array
63 */
64 protected $uploadedFiles;
65
66 /**
67 * Constructor, the only place to set all parameters of this Message/Request
68 *
69 * @param NULL|string $uri URI for the request, if any.
70 * @param NULL|string $method HTTP method for the request, if any.
71 * @param string|resource|StreamInterface $body Message body, if any.
72 * @param array $headers Headers for the message, if any.
73 * @param array $serverParams Server parameters, typically from $_SERVER
74 * @param array $uploadedFiles Upload file information, a tree of UploadedFiles
75 * @throws \InvalidArgumentException for any invalid value.
76 */
77 public function __construct($uri = NULL, $method = NULL, $body = 'php://input', array $headers = array(), array $serverParams = array(), array $uploadedFiles = NULL) {
78 if ($uploadedFiles !== NULL) {
79 $this->validateUploadedFiles($uploadedFiles);
80 }
81
82 parent::__construct($uri, $method, $body, $headers);
83
84 $this->serverParams = $serverParams;
85 $this->uploadedFiles = $uploadedFiles;
86 }
87
88 /**
89 * Retrieve server parameters.
90 *
91 * Retrieves data related to the incoming request environment,
92 * typically derived from PHP's $_SERVER superglobal. The data IS NOT
93 * REQUIRED to originate from $_SERVER.
94 *
95 * @return array
96 */
97 public function getServerParams() {
98 return $this->serverParams;
99 }
100
101 /**
102 * Retrieve cookies.
103 *
104 * Retrieves cookies sent by the client to the server.
105 *
106 * The data MUST be compatible with the structure of the $_COOKIE
107 * superglobal.
108 *
109 * @return array
110 */
111 public function getCookieParams() {
112 return $this->cookieParams;
113 }
114
115 /**
116 * Return an instance with the specified cookies.
117 *
118 * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
119 * be compatible with the structure of $_COOKIE. Typically, this data will
120 * be injected at instantiation.
121 *
122 * This method MUST NOT update the related Cookie header of the request
123 * instance, nor related values in the server params.
124 *
125 * This method MUST be implemented in such a way as to retain the
126 * immutability of the message, and MUST return an instance that has the
127 * updated cookie values.
128 *
129 * @param array $cookies Array of key/value pairs representing cookies.
130 * @return ServerRequest
131 */
132 public function withCookieParams(array $cookies) {
133 $clonedObject = clone $this;
134 $clonedObject->cookieParams = $cookies;
135 return $clonedObject;
136 }
137
138 /**
139 * Retrieve query string arguments.
140 *
141 * Retrieves the deserialized query string arguments, if any.
142 *
143 * Note: the query params might not be in sync with the URI or server
144 * params. If you need to ensure you are only getting the original
145 * values, you may need to parse the query string from `getUri()->getQuery()`
146 * or from the `QUERY_STRING` server param.
147 *
148 * @return array
149 */
150 public function getQueryParams() {
151 return $this->queryParams;
152 }
153
154 /**
155 * Return an instance with the specified query string arguments.
156 *
157 * These values SHOULD remain immutable over the course of the incoming
158 * request. They MAY be injected during instantiation, such as from PHP's
159 * $_GET superglobal, or MAY be derived from some other value such as the
160 * URI. In cases where the arguments are parsed from the URI, the data
161 * MUST be compatible with what PHP's parse_str() would return for
162 * purposes of how duplicate query parameters are handled, and how nested
163 * sets are handled.
164 *
165 * Setting query string arguments MUST NOT change the URI stored by the
166 * request, nor the values in the server params.
167 *
168 * This method MUST be implemented in such a way as to retain the
169 * immutability of the message, and MUST return an instance that has the
170 * updated query string arguments.
171 *
172 * @param array $query Array of query string arguments, typically from
173 * $_GET.
174 * @return ServerRequest
175 */
176 public function withQueryParams(array $query) {
177 $clonedObject = clone $this;
178 $clonedObject->queryParams = $query;
179 return $clonedObject;
180 }
181
182 /**
183 * Retrieve normalized file upload data.
184 *
185 * This method returns upload metadata in a normalized tree, with each leaf
186 * an instance of Psr\Http\Message\UploadedFileInterface.
187 *
188 * These values MAY be prepared from $_FILES or the message body during
189 * instantiation, or MAY be injected via withUploadedFiles().
190 *
191 * @return array An array tree of UploadedFileInterface instances; an empty
192 * array MUST be returned if no data is present.
193 */
194 public function getUploadedFiles() {
195 return $this->uploadedFiles;
196 }
197
198 /**
199 * Create a new instance with the specified uploaded files.
200 *
201 * This method MUST be implemented in such a way as to retain the
202 * immutability of the message, and MUST return an instance that has the
203 * updated body parameters.
204 *
205 * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
206 * @return ServerRequest
207 * @throws \InvalidArgumentException if an invalid structure is provided.
208 */
209 public function withUploadedFiles(array $uploadedFiles) {
210 $this->validateUploadedFiles($uploadedFiles);
211 $clonedObject = clone $this;
212 $clonedObject->uploadedFiles = $uploadedFiles;
213 return $clonedObject;
214 }
215
216 /**
217 * Retrieve any parameters provided in the request body.
218 *
219 * If the request Content-Type is either application/x-www-form-urlencoded
220 * or multipart/form-data, and the request method is POST, this method MUST
221 * return the contents of $_POST.
222 *
223 * Otherwise, this method may return any results of deserializing
224 * the request body content; as parsing returns structured content, the
225 * potential types MUST be arrays or objects only. A null value indicates
226 * the absence of body content.
227 *
228 * @return null|array|object The deserialized body parameters, if any.
229 * These will typically be an array or object.
230 */
231 public function getParsedBody() {
232 return $this->parsedBody;
233 }
234
235 /**
236 * Return an instance with the specified body parameters.
237 *
238 * These MAY be injected during instantiation.
239 *
240 * If the request Content-Type is either application/x-www-form-urlencoded
241 * or multipart/form-data, and the request method is POST, use this method
242 * ONLY to inject the contents of $_POST.
243 *
244 * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
245 * deserializing the request body content. Deserialization/parsing returns
246 * structured data, and, as such, this method ONLY accepts arrays or objects,
247 * or a null value if nothing was available to parse.
248 *
249 * As an example, if content negotiation determines that the request data
250 * is a JSON payload, this method could be used to create a request
251 * instance with the deserialized parameters.
252 *
253 * This method MUST be implemented in such a way as to retain the
254 * immutability of the message, and MUST return an instance that has the
255 * updated body parameters.
256 *
257 * @param null|array|object $data The deserialized body data. This will
258 * typically be in an array or object.
259 * @return ServerRequest
260 * @throws \InvalidArgumentException if an unsupported argument type is
261 * provided.
262 */
263 public function withParsedBody($data) {
264 $clonedObject = clone $this;
265 $clonedObject->parsedBody = $data;
266 return $clonedObject;
267 }
268
269 /**
270 * Retrieve attributes derived from the request.
271 *
272 * The request "attributes" may be used to allow injection of any
273 * parameters derived from the request: e.g., the results of path
274 * match operations; the results of decrypting cookies; the results of
275 * deserializing non-form-encoded message bodies; etc. Attributes
276 * will be application and request specific, and CAN be mutable.
277 *
278 * @return array Attributes derived from the request.
279 */
280 public function getAttributes() {
281 return $this->attributes;
282 }
283
284 /**
285 * Retrieve a single derived request attribute.
286 *
287 * Retrieves a single derived request attribute as described in
288 * getAttributes(). If the attribute has not been previously set, returns
289 * the default value as provided.
290 *
291 * This method obviates the need for a hasAttribute() method, as it allows
292 * specifying a default value to return if the attribute is not found.
293 *
294 * @see getAttributes()
295 *
296 * @param string $name The attribute name.
297 * @param mixed $default Default value to return if the attribute does not exist.
298 * @return mixed
299 */
300 public function getAttribute($name, $default = NULL) {
301 return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
302 }
303
304 /**
305 * Return an instance with the specified derived request attribute.
306 *
307 * This method allows setting a single derived request attribute as
308 * described in getAttributes().
309 *
310 * This method MUST be implemented in such a way as to retain the
311 * immutability of the message, and MUST return an instance that has the
312 * updated attribute.
313 *
314 * @see getAttributes()
315 *
316 * @param string $name The attribute name.
317 * @param mixed $value The value of the attribute.
318 * @return ServerRequest
319 */
320 public function withAttribute($name, $value) {
321 $clonedObject = clone $this;
322 $clonedObject->attributes[$name] = $value;
323 return $clonedObject;
324 }
325
326 /**
327 * Return an instance that removes the specified derived request attribute.
328 *
329 * This method allows removing a single derived request attribute as
330 * described in getAttributes().
331 *
332 * This method MUST be implemented in such a way as to retain the
333 * immutability of the message, and MUST return an instance that removes
334 * the attribute.
335 *
336 * @see getAttributes()
337 *
338 * @param string $name The attribute name.
339 * @return ServerRequest
340 */
341 public function withoutAttribute($name) {
342 $clonedObject = clone $this;
343 if (!isset($clonedObject->attributes[$name])) {
344 return $clonedObject;
345 } else {
346 unset($clonedObject->attributes[$name]);
347 return $clonedObject;
348 }
349 }
350
351 /**
352 * Recursively validate the structure in an uploaded files array.
353 *
354 * @param array $uploadedFiles
355 * @throws \InvalidArgumentException if any leaf is not an UploadedFileInterface instance.
356 */
357 protected function validateUploadedFiles(array $uploadedFiles) {
358 foreach ($uploadedFiles as $file) {
359 if (is_array($file)) {
360 $this->validateUploadedFiles($file);
361 continue;
362 }
363 if (!$file instanceof UploadedFileInterface) {
364 throw new \InvalidArgumentException('Invalid file in uploaded files structure.', 1436717281);
365 }
366 }
367 }
368
369 }