PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/Slim/Http/Request.php

http://github.com/codeguy/Slim
PHP | 1217 lines | 471 code | 123 blank | 623 comment | 52 complexity | 5201b403c8b396606a41d871d66a479b MD5 | raw file
  1. <?php
  2. /**
  3. * Slim Framework (https://slimframework.com)
  4. *
  5. * @link https://github.com/slimphp/Slim
  6. * @copyright Copyright (c) 2011-2017 Josh Lockhart
  7. * @license https://github.com/slimphp/Slim/blob/3.x/LICENSE.md (MIT License)
  8. */
  9. namespace Slim\Http;
  10. use Closure;
  11. use InvalidArgumentException;
  12. use Psr\Http\Message\UploadedFileInterface;
  13. use RuntimeException;
  14. use Psr\Http\Message\ServerRequestInterface;
  15. use Psr\Http\Message\UriInterface;
  16. use Psr\Http\Message\StreamInterface;
  17. use Slim\Collection;
  18. use Slim\Exception\InvalidMethodException;
  19. use Slim\Exception\MethodNotAllowedException;
  20. use Slim\Interfaces\Http\HeadersInterface;
  21. /**
  22. * Request
  23. *
  24. * This class represents an HTTP request. It manages
  25. * the request method, URI, headers, cookies, and body
  26. * according to the PSR-7 standard.
  27. *
  28. * @link https://github.com/php-fig/http-message/blob/master/src/MessageInterface.php
  29. * @link https://github.com/php-fig/http-message/blob/master/src/RequestInterface.php
  30. * @link https://github.com/php-fig/http-message/blob/master/src/ServerRequestInterface.php
  31. */
  32. class Request extends Message implements ServerRequestInterface
  33. {
  34. /**
  35. * The request method
  36. *
  37. * @var string
  38. */
  39. protected $method;
  40. /**
  41. * The original request method (ignoring override)
  42. *
  43. * @var string
  44. */
  45. protected $originalMethod;
  46. /**
  47. * The request URI object
  48. *
  49. * @var \Psr\Http\Message\UriInterface
  50. */
  51. protected $uri;
  52. /**
  53. * The request URI target (path + query string)
  54. *
  55. * @var string
  56. */
  57. protected $requestTarget;
  58. /**
  59. * The request query string params
  60. *
  61. * @var array
  62. */
  63. protected $queryParams;
  64. /**
  65. * The request cookies
  66. *
  67. * @var array
  68. */
  69. protected $cookies;
  70. /**
  71. * The server environment variables at the time the request was created.
  72. *
  73. * @var array
  74. */
  75. protected $serverParams;
  76. /**
  77. * The request attributes (route segment names and values)
  78. *
  79. * @var \Slim\Collection
  80. */
  81. protected $attributes;
  82. /**
  83. * The request body parsed (if possible) into a PHP array or object
  84. *
  85. * @var null|array|object
  86. */
  87. protected $bodyParsed = false;
  88. /**
  89. * List of request body parsers (e.g., url-encoded, JSON, XML, multipart)
  90. *
  91. * @var callable[]
  92. */
  93. protected $bodyParsers = [];
  94. /**
  95. * List of uploaded files
  96. *
  97. * @var UploadedFileInterface[]
  98. */
  99. protected $uploadedFiles;
  100. /**
  101. * Valid request methods
  102. *
  103. * @var string[]
  104. * @deprecated
  105. */
  106. protected $validMethods = [
  107. 'CONNECT' => 1,
  108. 'DELETE' => 1,
  109. 'GET' => 1,
  110. 'HEAD' => 1,
  111. 'OPTIONS' => 1,
  112. 'PATCH' => 1,
  113. 'POST' => 1,
  114. 'PUT' => 1,
  115. 'TRACE' => 1,
  116. ];
  117. /**
  118. * Create new HTTP request with data extracted from the application
  119. * Environment object
  120. *
  121. * @param Environment $environment The Slim application Environment
  122. *
  123. * @return static
  124. */
  125. public static function createFromEnvironment(Environment $environment)
  126. {
  127. $method = $environment['REQUEST_METHOD'];
  128. $uri = Uri::createFromEnvironment($environment);
  129. $headers = Headers::createFromEnvironment($environment);
  130. $cookies = Cookies::parseHeader($headers->get('Cookie', []));
  131. $serverParams = $environment->all();
  132. $body = new RequestBody();
  133. $uploadedFiles = UploadedFile::createFromEnvironment($environment);
  134. $request = new static($method, $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
  135. if ($method === 'POST' &&
  136. in_array($request->getMediaType(), ['application/x-www-form-urlencoded', 'multipart/form-data'])
  137. ) {
  138. // parsed body must be $_POST
  139. $request = $request->withParsedBody($_POST);
  140. }
  141. return $request;
  142. }
  143. /**
  144. * Create new HTTP request.
  145. *
  146. * Adds a host header when none was provided and a host is defined in uri.
  147. *
  148. * @param string $method The request method
  149. * @param UriInterface $uri The request URI object
  150. * @param HeadersInterface $headers The request headers collection
  151. * @param array $cookies The request cookies collection
  152. * @param array $serverParams The server environment variables
  153. * @param StreamInterface $body The request body object
  154. * @param array $uploadedFiles The request uploadedFiles collection
  155. * @throws InvalidMethodException on invalid HTTP method
  156. */
  157. public function __construct(
  158. $method,
  159. UriInterface $uri,
  160. HeadersInterface $headers,
  161. array $cookies,
  162. array $serverParams,
  163. StreamInterface $body,
  164. array $uploadedFiles = []
  165. ) {
  166. try {
  167. $this->originalMethod = $this->filterMethod($method);
  168. } catch (InvalidMethodException $e) {
  169. $this->originalMethod = $method;
  170. }
  171. $this->uri = $uri;
  172. $this->headers = $headers;
  173. $this->cookies = $cookies;
  174. $this->serverParams = $serverParams;
  175. $this->attributes = new Collection();
  176. $this->body = $body;
  177. $this->uploadedFiles = $uploadedFiles;
  178. if (isset($serverParams['SERVER_PROTOCOL'])) {
  179. $this->protocolVersion = str_replace('HTTP/', '', $serverParams['SERVER_PROTOCOL']);
  180. }
  181. if (!$this->headers->has('Host') || $this->uri->getHost() !== '') {
  182. $this->headers->set('Host', $this->uri->getHost());
  183. }
  184. $this->registerMediaTypeParser('application/json', function ($input) {
  185. $result = json_decode($input, true);
  186. if (!is_array($result)) {
  187. return null;
  188. }
  189. return $result;
  190. });
  191. $this->registerMediaTypeParser('application/xml', function ($input) {
  192. $backup = libxml_disable_entity_loader(true);
  193. $backup_errors = libxml_use_internal_errors(true);
  194. $result = simplexml_load_string($input);
  195. libxml_disable_entity_loader($backup);
  196. libxml_clear_errors();
  197. libxml_use_internal_errors($backup_errors);
  198. if ($result === false) {
  199. return null;
  200. }
  201. return $result;
  202. });
  203. $this->registerMediaTypeParser('text/xml', function ($input) {
  204. $backup = libxml_disable_entity_loader(true);
  205. $backup_errors = libxml_use_internal_errors(true);
  206. $result = simplexml_load_string($input);
  207. libxml_disable_entity_loader($backup);
  208. libxml_clear_errors();
  209. libxml_use_internal_errors($backup_errors);
  210. if ($result === false) {
  211. return null;
  212. }
  213. return $result;
  214. });
  215. $this->registerMediaTypeParser('application/x-www-form-urlencoded', function ($input) {
  216. parse_str($input, $data);
  217. return $data;
  218. });
  219. // if the request had an invalid method, we can throw it now
  220. if (isset($e) && $e instanceof InvalidMethodException) {
  221. throw $e;
  222. }
  223. }
  224. /**
  225. * This method is applied to the cloned object
  226. * after PHP performs an initial shallow-copy. This
  227. * method completes a deep-copy by creating new objects
  228. * for the cloned object's internal reference pointers.
  229. */
  230. public function __clone()
  231. {
  232. $this->headers = clone $this->headers;
  233. $this->attributes = clone $this->attributes;
  234. $this->body = clone $this->body;
  235. }
  236. /*******************************************************************************
  237. * Method
  238. ******************************************************************************/
  239. /**
  240. * Retrieves the HTTP method of the request.
  241. *
  242. * @return string Returns the request method.
  243. */
  244. public function getMethod()
  245. {
  246. if ($this->method === null) {
  247. $this->method = $this->originalMethod;
  248. $customMethod = $this->getHeaderLine('X-Http-Method-Override');
  249. if ($customMethod) {
  250. $this->method = $this->filterMethod($customMethod);
  251. } elseif ($this->originalMethod === 'POST') {
  252. $overrideMethod = $this->filterMethod($this->getParsedBodyParam('_METHOD'));
  253. if ($overrideMethod !== null) {
  254. $this->method = $overrideMethod;
  255. }
  256. if ($this->getBody()->eof()) {
  257. $this->getBody()->rewind();
  258. }
  259. }
  260. }
  261. return $this->method;
  262. }
  263. /**
  264. * Get the original HTTP method (ignore override).
  265. *
  266. * Note: This method is not part of the PSR-7 standard.
  267. *
  268. * @return string
  269. */
  270. public function getOriginalMethod()
  271. {
  272. return $this->originalMethod;
  273. }
  274. /**
  275. * Return an instance with the provided HTTP method.
  276. *
  277. * While HTTP method names are typically all uppercase characters, HTTP
  278. * method names are case-sensitive and thus implementations SHOULD NOT
  279. * modify the given string.
  280. *
  281. * This method MUST be implemented in such a way as to retain the
  282. * immutability of the message, and MUST return an instance that has the
  283. * changed request method.
  284. *
  285. * @param string $method Case-sensitive method.
  286. * @return static
  287. * @throws \InvalidArgumentException for invalid HTTP methods.
  288. */
  289. public function withMethod($method)
  290. {
  291. $method = $this->filterMethod($method);
  292. $clone = clone $this;
  293. $clone->originalMethod = $method;
  294. $clone->method = $method;
  295. return $clone;
  296. }
  297. /**
  298. * Validate the HTTP method
  299. *
  300. * @param null|string $method
  301. * @return null|string
  302. * @throws \InvalidArgumentException on invalid HTTP method.
  303. */
  304. protected function filterMethod($method)
  305. {
  306. if ($method === null) {
  307. return $method;
  308. }
  309. if (!is_string($method)) {
  310. throw new InvalidArgumentException(sprintf(
  311. 'Unsupported HTTP method; must be a string, received %s',
  312. (is_object($method) ? get_class($method) : gettype($method))
  313. ));
  314. }
  315. $method = strtoupper($method);
  316. if (preg_match("/^[!#$%&'*+.^_`|~0-9a-z-]+$/i", $method) !== 1) {
  317. throw new InvalidMethodException($this, $method);
  318. }
  319. return $method;
  320. }
  321. /**
  322. * Does this request use a given method?
  323. *
  324. * Note: This method is not part of the PSR-7 standard.
  325. *
  326. * @param string $method HTTP method
  327. * @return bool
  328. */
  329. public function isMethod($method)
  330. {
  331. return $this->getMethod() === $method;
  332. }
  333. /**
  334. * Is this a GET request?
  335. *
  336. * Note: This method is not part of the PSR-7 standard.
  337. *
  338. * @return bool
  339. */
  340. public function isGet()
  341. {
  342. return $this->isMethod('GET');
  343. }
  344. /**
  345. * Is this a POST request?
  346. *
  347. * Note: This method is not part of the PSR-7 standard.
  348. *
  349. * @return bool
  350. */
  351. public function isPost()
  352. {
  353. return $this->isMethod('POST');
  354. }
  355. /**
  356. * Is this a PUT request?
  357. *
  358. * Note: This method is not part of the PSR-7 standard.
  359. *
  360. * @return bool
  361. */
  362. public function isPut()
  363. {
  364. return $this->isMethod('PUT');
  365. }
  366. /**
  367. * Is this a PATCH request?
  368. *
  369. * Note: This method is not part of the PSR-7 standard.
  370. *
  371. * @return bool
  372. */
  373. public function isPatch()
  374. {
  375. return $this->isMethod('PATCH');
  376. }
  377. /**
  378. * Is this a DELETE request?
  379. *
  380. * Note: This method is not part of the PSR-7 standard.
  381. *
  382. * @return bool
  383. */
  384. public function isDelete()
  385. {
  386. return $this->isMethod('DELETE');
  387. }
  388. /**
  389. * Is this a HEAD request?
  390. *
  391. * Note: This method is not part of the PSR-7 standard.
  392. *
  393. * @return bool
  394. */
  395. public function isHead()
  396. {
  397. return $this->isMethod('HEAD');
  398. }
  399. /**
  400. * Is this a OPTIONS request?
  401. *
  402. * Note: This method is not part of the PSR-7 standard.
  403. *
  404. * @return bool
  405. */
  406. public function isOptions()
  407. {
  408. return $this->isMethod('OPTIONS');
  409. }
  410. /**
  411. * Is this an XHR request?
  412. *
  413. * Note: This method is not part of the PSR-7 standard.
  414. *
  415. * @return bool
  416. */
  417. public function isXhr()
  418. {
  419. return $this->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
  420. }
  421. /*******************************************************************************
  422. * URI
  423. ******************************************************************************/
  424. /**
  425. * Retrieves the message's request target.
  426. *
  427. * Retrieves the message's request-target either as it will appear (for
  428. * clients), as it appeared at request (for servers), or as it was
  429. * specified for the instance (see withRequestTarget()).
  430. *
  431. * In most cases, this will be the origin-form of the composed URI,
  432. * unless a value was provided to the concrete implementation (see
  433. * withRequestTarget() below).
  434. *
  435. * If no URI is available, and no request-target has been specifically
  436. * provided, this method MUST return the string "/".
  437. *
  438. * @return string
  439. */
  440. public function getRequestTarget()
  441. {
  442. if ($this->requestTarget) {
  443. return $this->requestTarget;
  444. }
  445. if ($this->uri === null) {
  446. return '/';
  447. }
  448. $basePath = $this->uri->getBasePath();
  449. $path = $this->uri->getPath();
  450. $path = $basePath . '/' . ltrim($path, '/');
  451. $query = $this->uri->getQuery();
  452. if ($query) {
  453. $path .= '?' . $query;
  454. }
  455. $this->requestTarget = $path;
  456. return $this->requestTarget;
  457. }
  458. /**
  459. * Return an instance with the specific request-target.
  460. *
  461. * If the request needs a non-origin-form request-target — e.g., for
  462. * specifying an absolute-form, authority-form, or asterisk-form —
  463. * this method may be used to create an instance with the specified
  464. * request-target, verbatim.
  465. *
  466. * This method MUST be implemented in such a way as to retain the
  467. * immutability of the message, and MUST return an instance that has the
  468. * changed request target.
  469. *
  470. * @link http://tools.ietf.org/html/rfc7230#section-2.7 (for the various
  471. * request-target forms allowed in request messages)
  472. * @param mixed $requestTarget
  473. * @return static
  474. * @throws InvalidArgumentException if the request target is invalid
  475. */
  476. public function withRequestTarget($requestTarget)
  477. {
  478. if (preg_match('#\s#', $requestTarget)) {
  479. throw new InvalidArgumentException(
  480. 'Invalid request target provided; must be a string and cannot contain whitespace'
  481. );
  482. }
  483. $clone = clone $this;
  484. $clone->requestTarget = $requestTarget;
  485. return $clone;
  486. }
  487. /**
  488. * Retrieves the URI instance.
  489. *
  490. * This method MUST return a UriInterface instance.
  491. *
  492. * @link http://tools.ietf.org/html/rfc3986#section-4.3
  493. * @return UriInterface Returns a UriInterface instance
  494. * representing the URI of the request.
  495. */
  496. public function getUri()
  497. {
  498. return $this->uri;
  499. }
  500. /**
  501. * Returns an instance with the provided URI.
  502. *
  503. * This method MUST update the Host header of the returned request by
  504. * default if the URI contains a host component. If the URI does not
  505. * contain a host component, any pre-existing Host header MUST be carried
  506. * over to the returned request.
  507. *
  508. * You can opt-in to preserving the original state of the Host header by
  509. * setting `$preserveHost` to `true`. When `$preserveHost` is set to
  510. * `true`, this method interacts with the Host header in the following ways:
  511. *
  512. * - If the the Host header is missing or empty, and the new URI contains
  513. * a host component, this method MUST update the Host header in the returned
  514. * request.
  515. * - If the Host header is missing or empty, and the new URI does not contain a
  516. * host component, this method MUST NOT update the Host header in the returned
  517. * request.
  518. * - If a Host header is present and non-empty, this method MUST NOT update
  519. * the Host header in the returned request.
  520. *
  521. * This method MUST be implemented in such a way as to retain the
  522. * immutability of the message, and MUST return an instance that has the
  523. * new UriInterface instance.
  524. *
  525. * @link http://tools.ietf.org/html/rfc3986#section-4.3
  526. * @param UriInterface $uri New request URI to use.
  527. * @param bool $preserveHost Preserve the original state of the Host header.
  528. * @return static
  529. */
  530. public function withUri(UriInterface $uri, $preserveHost = false)
  531. {
  532. $clone = clone $this;
  533. $clone->uri = $uri;
  534. if (!$preserveHost) {
  535. if ($uri->getHost() !== '') {
  536. $clone->headers->set('Host', $uri->getHost());
  537. }
  538. } else {
  539. if ($uri->getHost() !== '' && (!$this->hasHeader('Host') || $this->getHeaderLine('Host') === '')) {
  540. $clone->headers->set('Host', $uri->getHost());
  541. }
  542. }
  543. return $clone;
  544. }
  545. /**
  546. * Get request content type.
  547. *
  548. * Note: This method is not part of the PSR-7 standard.
  549. *
  550. * @return string|null The request content type, if known
  551. */
  552. public function getContentType()
  553. {
  554. $result = $this->getHeader('Content-Type');
  555. return $result ? $result[0] : null;
  556. }
  557. /**
  558. * Get request media type, if known.
  559. *
  560. * Note: This method is not part of the PSR-7 standard.
  561. *
  562. * @return string|null The request media type, minus content-type params
  563. */
  564. public function getMediaType()
  565. {
  566. $contentType = $this->getContentType();
  567. if ($contentType) {
  568. $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
  569. return strtolower($contentTypeParts[0]);
  570. }
  571. return null;
  572. }
  573. /**
  574. * Get request media type params, if known.
  575. *
  576. * Note: This method is not part of the PSR-7 standard.
  577. *
  578. * @return array
  579. */
  580. public function getMediaTypeParams()
  581. {
  582. $contentType = $this->getContentType();
  583. $contentTypeParams = [];
  584. if ($contentType) {
  585. $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
  586. $contentTypePartsLength = count($contentTypeParts);
  587. for ($i = 1; $i < $contentTypePartsLength; $i++) {
  588. $paramParts = explode('=', $contentTypeParts[$i]);
  589. $contentTypeParams[strtolower($paramParts[0])] = $paramParts[1];
  590. }
  591. }
  592. return $contentTypeParams;
  593. }
  594. /**
  595. * Get request content character set, if known.
  596. *
  597. * Note: This method is not part of the PSR-7 standard.
  598. *
  599. * @return string|null
  600. */
  601. public function getContentCharset()
  602. {
  603. $mediaTypeParams = $this->getMediaTypeParams();
  604. if (isset($mediaTypeParams['charset'])) {
  605. return $mediaTypeParams['charset'];
  606. }
  607. return null;
  608. }
  609. /**
  610. * Get request content length, if known.
  611. *
  612. * Note: This method is not part of the PSR-7 standard.
  613. *
  614. * @return int|null
  615. */
  616. public function getContentLength()
  617. {
  618. $result = $this->headers->get('Content-Length');
  619. return $result ? (int)$result[0] : null;
  620. }
  621. /*******************************************************************************
  622. * Cookies
  623. ******************************************************************************/
  624. /**
  625. * Retrieve cookies.
  626. *
  627. * Retrieves cookies sent by the client to the server.
  628. *
  629. * The data MUST be compatible with the structure of the $_COOKIE
  630. * superglobal.
  631. *
  632. * @return array
  633. */
  634. public function getCookieParams()
  635. {
  636. return $this->cookies;
  637. }
  638. /**
  639. * Fetch cookie value from cookies sent by the client to the server.
  640. *
  641. * Note: This method is not part of the PSR-7 standard.
  642. *
  643. * @param string $key The attribute name.
  644. * @param mixed $default Default value to return if the attribute does not exist.
  645. *
  646. * @return mixed
  647. */
  648. public function getCookieParam($key, $default = null)
  649. {
  650. $cookies = $this->getCookieParams();
  651. $result = $default;
  652. if (isset($cookies[$key])) {
  653. $result = $cookies[$key];
  654. }
  655. return $result;
  656. }
  657. /**
  658. * Return an instance with the specified cookies.
  659. *
  660. * The data IS NOT REQUIRED to come from the $_COOKIE superglobal, but MUST
  661. * be compatible with the structure of $_COOKIE. Typically, this data will
  662. * be injected at instantiation.
  663. *
  664. * This method MUST NOT update the related Cookie header of the request
  665. * instance, nor related values in the server params.
  666. *
  667. * This method MUST be implemented in such a way as to retain the
  668. * immutability of the message, and MUST return an instance that has the
  669. * updated cookie values.
  670. *
  671. * @param array $cookies Array of key/value pairs representing cookies.
  672. * @return static
  673. */
  674. public function withCookieParams(array $cookies)
  675. {
  676. $clone = clone $this;
  677. $clone->cookies = $cookies;
  678. return $clone;
  679. }
  680. /*******************************************************************************
  681. * Query Params
  682. ******************************************************************************/
  683. /**
  684. * Retrieve query string arguments.
  685. *
  686. * Retrieves the deserialized query string arguments, if any.
  687. *
  688. * Note: the query params might not be in sync with the URI or server
  689. * params. If you need to ensure you are only getting the original
  690. * values, you may need to parse the query string from `getUri()->getQuery()`
  691. * or from the `QUERY_STRING` server param.
  692. *
  693. * @return array
  694. */
  695. public function getQueryParams()
  696. {
  697. if (is_array($this->queryParams)) {
  698. return $this->queryParams;
  699. }
  700. if ($this->uri === null) {
  701. return [];
  702. }
  703. parse_str($this->uri->getQuery(), $this->queryParams); // <-- URL decodes data
  704. return $this->queryParams;
  705. }
  706. /**
  707. * Return an instance with the specified query string arguments.
  708. *
  709. * These values SHOULD remain immutable over the course of the incoming
  710. * request. They MAY be injected during instantiation, such as from PHP's
  711. * $_GET superglobal, or MAY be derived from some other value such as the
  712. * URI. In cases where the arguments are parsed from the URI, the data
  713. * MUST be compatible with what PHP's parse_str() would return for
  714. * purposes of how duplicate query parameters are handled, and how nested
  715. * sets are handled.
  716. *
  717. * Setting query string arguments MUST NOT change the URI stored by the
  718. * request, nor the values in the server params.
  719. *
  720. * This method MUST be implemented in such a way as to retain the
  721. * immutability of the message, and MUST return an instance that has the
  722. * updated query string arguments.
  723. *
  724. * @param array $query Array of query string arguments, typically from
  725. * $_GET.
  726. * @return static
  727. */
  728. public function withQueryParams(array $query)
  729. {
  730. $clone = clone $this;
  731. $clone->queryParams = $query;
  732. return $clone;
  733. }
  734. /*******************************************************************************
  735. * File Params
  736. ******************************************************************************/
  737. /**
  738. * Retrieve normalized file upload data.
  739. *
  740. * This method returns upload metadata in a normalized tree, with each leaf
  741. * an instance of Psr\Http\Message\UploadedFileInterface.
  742. *
  743. * These values MAY be prepared from $_FILES or the message body during
  744. * instantiation, or MAY be injected via withUploadedFiles().
  745. *
  746. * @return array An array tree of UploadedFileInterface instances; an empty
  747. * array MUST be returned if no data is present.
  748. */
  749. public function getUploadedFiles()
  750. {
  751. return $this->uploadedFiles;
  752. }
  753. /**
  754. * Create a new instance with the specified uploaded files.
  755. *
  756. * This method MUST be implemented in such a way as to retain the
  757. * immutability of the message, and MUST return an instance that has the
  758. * updated body parameters.
  759. *
  760. * @param array $uploadedFiles An array tree of UploadedFileInterface instances.
  761. * @return static
  762. * @throws \InvalidArgumentException if an invalid structure is provided.
  763. */
  764. public function withUploadedFiles(array $uploadedFiles)
  765. {
  766. $clone = clone $this;
  767. $clone->uploadedFiles = $uploadedFiles;
  768. return $clone;
  769. }
  770. /*******************************************************************************
  771. * Server Params
  772. ******************************************************************************/
  773. /**
  774. * Retrieve server parameters.
  775. *
  776. * Retrieves data related to the incoming request environment,
  777. * typically derived from PHP's $_SERVER superglobal. The data IS NOT
  778. * REQUIRED to originate from $_SERVER.
  779. *
  780. * @return array
  781. */
  782. public function getServerParams()
  783. {
  784. return $this->serverParams;
  785. }
  786. /**
  787. * Retrieve a server parameter.
  788. *
  789. * Note: This method is not part of the PSR-7 standard.
  790. *
  791. * @param string $key
  792. * @param mixed $default
  793. * @return mixed
  794. */
  795. public function getServerParam($key, $default = null)
  796. {
  797. $serverParams = $this->getServerParams();
  798. return isset($serverParams[$key]) ? $serverParams[$key] : $default;
  799. }
  800. /*******************************************************************************
  801. * Attributes
  802. ******************************************************************************/
  803. /**
  804. * Retrieve attributes derived from the request.
  805. *
  806. * The request "attributes" may be used to allow injection of any
  807. * parameters derived from the request: e.g., the results of path
  808. * match operations; the results of decrypting cookies; the results of
  809. * deserializing non-form-encoded message bodies; etc. Attributes
  810. * will be application and request specific, and CAN be mutable.
  811. *
  812. * @return array Attributes derived from the request.
  813. */
  814. public function getAttributes()
  815. {
  816. return $this->attributes->all();
  817. }
  818. /**
  819. * Retrieve a single derived request attribute.
  820. *
  821. * Retrieves a single derived request attribute as described in
  822. * getAttributes(). If the attribute has not been previously set, returns
  823. * the default value as provided.
  824. *
  825. * This method obviates the need for a hasAttribute() method, as it allows
  826. * specifying a default value to return if the attribute is not found.
  827. *
  828. * @see getAttributes()
  829. * @param string $name The attribute name.
  830. * @param mixed $default Default value to return if the attribute does not exist.
  831. * @return mixed
  832. */
  833. public function getAttribute($name, $default = null)
  834. {
  835. return $this->attributes->get($name, $default);
  836. }
  837. /**
  838. * Return an instance with the specified derived request attribute.
  839. *
  840. * This method allows setting a single derived request attribute as
  841. * described in getAttributes().
  842. *
  843. * This method MUST be implemented in such a way as to retain the
  844. * immutability of the message, and MUST return an instance that has the
  845. * updated attribute.
  846. *
  847. * @see getAttributes()
  848. * @param string $name The attribute name.
  849. * @param mixed $value The value of the attribute.
  850. * @return static
  851. */
  852. public function withAttribute($name, $value)
  853. {
  854. $clone = clone $this;
  855. $clone->attributes->set($name, $value);
  856. return $clone;
  857. }
  858. /**
  859. * Create a new instance with the specified derived request attributes.
  860. *
  861. * Note: This method is not part of the PSR-7 standard.
  862. *
  863. * This method allows setting all new derived request attributes as
  864. * described in getAttributes().
  865. *
  866. * This method MUST be implemented in such a way as to retain the
  867. * immutability of the message, and MUST return a new instance that has the
  868. * updated attributes.
  869. *
  870. * @param array $attributes New attributes
  871. * @return static
  872. */
  873. public function withAttributes(array $attributes)
  874. {
  875. $clone = clone $this;
  876. $clone->attributes = new Collection($attributes);
  877. return $clone;
  878. }
  879. /**
  880. * Return an instance that removes the specified derived request attribute.
  881. *
  882. * This method allows removing a single derived request attribute as
  883. * described in getAttributes().
  884. *
  885. * This method MUST be implemented in such a way as to retain the
  886. * immutability of the message, and MUST return an instance that removes
  887. * the attribute.
  888. *
  889. * @see getAttributes()
  890. * @param string $name The attribute name.
  891. * @return static
  892. */
  893. public function withoutAttribute($name)
  894. {
  895. $clone = clone $this;
  896. $clone->attributes->remove($name);
  897. return $clone;
  898. }
  899. /*******************************************************************************
  900. * Body
  901. ******************************************************************************/
  902. /**
  903. * Retrieve any parameters provided in the request body.
  904. *
  905. * If the request Content-Type is either application/x-www-form-urlencoded
  906. * or multipart/form-data, and the request method is POST, this method MUST
  907. * return the contents of $_POST.
  908. *
  909. * Otherwise, this method may return any results of deserializing
  910. * the request body content; as parsing returns structured content, the
  911. * potential types MUST be arrays or objects only. A null value indicates
  912. * the absence of body content.
  913. *
  914. * @return null|array|object The deserialized body parameters, if any.
  915. * These will typically be an array or object.
  916. * @throws RuntimeException if the request body media type parser returns an invalid value
  917. */
  918. public function getParsedBody()
  919. {
  920. if ($this->bodyParsed !== false) {
  921. return $this->bodyParsed;
  922. }
  923. if (!$this->body) {
  924. return null;
  925. }
  926. $mediaType = $this->getMediaType();
  927. // look for a media type with a structured syntax suffix (RFC 6839)
  928. $parts = explode('+', $mediaType);
  929. if (count($parts) >= 2) {
  930. $mediaType = 'application/' . $parts[count($parts)-1];
  931. }
  932. if (isset($this->bodyParsers[$mediaType]) === true) {
  933. $body = (string)$this->getBody();
  934. $parsed = $this->bodyParsers[$mediaType]($body);
  935. if (!is_null($parsed) && !is_object($parsed) && !is_array($parsed)) {
  936. throw new RuntimeException(
  937. 'Request body media type parser return value must be an array, an object, or null'
  938. );
  939. }
  940. $this->bodyParsed = $parsed;
  941. return $this->bodyParsed;
  942. }
  943. return null;
  944. }
  945. /**
  946. * Return an instance with the specified body parameters.
  947. *
  948. * These MAY be injected during instantiation.
  949. *
  950. * If the request Content-Type is either application/x-www-form-urlencoded
  951. * or multipart/form-data, and the request method is POST, use this method
  952. * ONLY to inject the contents of $_POST.
  953. *
  954. * The data IS NOT REQUIRED to come from $_POST, but MUST be the results of
  955. * deserializing the request body content. Deserialization/parsing returns
  956. * structured data, and, as such, this method ONLY accepts arrays or objects,
  957. * or a null value if nothing was available to parse.
  958. *
  959. * As an example, if content negotiation determines that the request data
  960. * is a JSON payload, this method could be used to create a request
  961. * instance with the deserialized parameters.
  962. *
  963. * This method MUST be implemented in such a way as to retain the
  964. * immutability of the message, and MUST return an instance that has the
  965. * updated body parameters.
  966. *
  967. * @param null|array|object $data The deserialized body data. This will
  968. * typically be in an array or object.
  969. * @return static
  970. * @throws \InvalidArgumentException if an unsupported argument type is
  971. * provided.
  972. */
  973. public function withParsedBody($data)
  974. {
  975. if (!is_null($data) && !is_object($data) && !is_array($data)) {
  976. throw new InvalidArgumentException('Parsed body value must be an array, an object, or null');
  977. }
  978. $clone = clone $this;
  979. $clone->bodyParsed = $data;
  980. return $clone;
  981. }
  982. /**
  983. * Force Body to be parsed again.
  984. *
  985. * Note: This method is not part of the PSR-7 standard.
  986. *
  987. * @return $this
  988. */
  989. public function reparseBody()
  990. {
  991. $this->bodyParsed = false;
  992. return $this;
  993. }
  994. /**
  995. * Register media type parser.
  996. *
  997. * Note: This method is not part of the PSR-7 standard.
  998. *
  999. * @param string $mediaType A HTTP media type (excluding content-type
  1000. * params).
  1001. * @param callable $callable A callable that returns parsed contents for
  1002. * media type.
  1003. */
  1004. public function registerMediaTypeParser($mediaType, callable $callable)
  1005. {
  1006. if ($callable instanceof Closure) {
  1007. $callable = $callable->bindTo($this);
  1008. }
  1009. $this->bodyParsers[(string)$mediaType] = $callable;
  1010. }
  1011. /*******************************************************************************
  1012. * Parameters (e.g., POST and GET data)
  1013. ******************************************************************************/
  1014. /**
  1015. * Fetch request parameter value from body or query string (in that order).
  1016. *
  1017. * Note: This method is not part of the PSR-7 standard.
  1018. *
  1019. * @param string $key The parameter key.
  1020. * @param string $default The default value.
  1021. *
  1022. * @return mixed The parameter value.
  1023. */
  1024. public function getParam($key, $default = null)
  1025. {
  1026. $postParams = $this->getParsedBody();
  1027. $getParams = $this->getQueryParams();
  1028. $result = $default;
  1029. if (is_array($postParams) && isset($postParams[$key])) {
  1030. $result = $postParams[$key];
  1031. } elseif (is_object($postParams) && property_exists($postParams, $key)) {
  1032. $result = $postParams->$key;
  1033. } elseif (isset($getParams[$key])) {
  1034. $result = $getParams[$key];
  1035. }
  1036. return $result;
  1037. }
  1038. /**
  1039. * Fetch parameter value from request body.
  1040. *
  1041. * Note: This method is not part of the PSR-7 standard.
  1042. *
  1043. * @param string $key
  1044. * @param mixed $default
  1045. *
  1046. * @return mixed
  1047. */
  1048. public function getParsedBodyParam($key, $default = null)
  1049. {
  1050. $postParams = $this->getParsedBody();
  1051. $result = $default;
  1052. if (is_array($postParams) && isset($postParams[$key])) {
  1053. $result = $postParams[$key];
  1054. } elseif (is_object($postParams) && property_exists($postParams, $key)) {
  1055. $result = $postParams->$key;
  1056. }
  1057. return $result;
  1058. }
  1059. /**
  1060. * Fetch parameter value from query string.
  1061. *
  1062. * Note: This method is not part of the PSR-7 standard.
  1063. *
  1064. * @param string $key
  1065. * @param mixed $default
  1066. *
  1067. * @return mixed
  1068. */
  1069. public function getQueryParam($key, $default = null)
  1070. {
  1071. $getParams = $this->getQueryParams();
  1072. $result = $default;
  1073. if (isset($getParams[$key])) {
  1074. $result = $getParams[$key];
  1075. }
  1076. return $result;
  1077. }
  1078. /**
  1079. * Fetch associative array of body and query string parameters.
  1080. *
  1081. * Note: This method is not part of the PSR-7 standard.
  1082. *
  1083. * @return array
  1084. */
  1085. public function getParams()
  1086. {
  1087. $params = $this->getQueryParams();
  1088. $postParams = $this->getParsedBody();
  1089. if ($postParams) {
  1090. $params = array_merge($params, (array)$postParams);
  1091. }
  1092. return $params;
  1093. }
  1094. }