1: <?php declare(strict_types=1);
2:
3: namespace Salient\Http;
4:
5: use Psr\Http\Message\MessageInterface;
6: use Psr\Http\Message\ServerRequestInterface;
7: use Psr\Http\Message\StreamInterface;
8: use Psr\Http\Message\UriInterface as PsrUriInterface;
9: use Salient\Contract\Core\Arrayable;
10: use Salient\Contract\Http\HttpServerRequestInterface;
11: use Salient\Core\Concern\HasMutator;
12: use Salient\Utility\Exception\InvalidArgumentTypeException;
13: use Stringable;
14:
15: /**
16: * A PSR-7 request (incoming, server-side)
17: *
18: * @api
19: */
20: class HttpServerRequest extends HttpRequest implements HttpServerRequestInterface
21: {
22: use HasMutator;
23:
24: /** @var mixed[] */
25: protected array $ServerParams;
26: /** @var mixed[] */
27: protected array $CookieParams = [];
28: /** @var mixed[] */
29: protected array $QueryParams = [];
30: /** @var mixed[] */
31: protected array $UploadedFiles = [];
32: /** @var mixed[]|object|null */
33: protected $ParsedBody;
34: /** @var array<string,mixed> */
35: protected array $Attributes = [];
36:
37: /**
38: * Creates a new HttpServerRequest object
39: *
40: * @param PsrUriInterface|Stringable|string $uri
41: * @param mixed[] $serverParams
42: * @param StreamInterface|resource|string|null $body
43: * @param Arrayable<string,string[]|string>|iterable<string,string[]|string>|null $headers
44: */
45: public function __construct(
46: string $method,
47: $uri,
48: array $serverParams = [],
49: $body = null,
50: $headers = null,
51: ?string $requestTarget = null,
52: string $version = '1.1'
53: ) {
54: $this->ServerParams = $serverParams;
55:
56: parent::__construct($method, $uri, $body, $headers, $requestTarget, $version);
57: }
58:
59: /**
60: * @inheritDoc
61: */
62: public static function fromPsr7(MessageInterface $message): HttpServerRequest
63: {
64: if ($message instanceof HttpServerRequest) {
65: return $message;
66: }
67:
68: if (!$message instanceof ServerRequestInterface) {
69: throw new InvalidArgumentTypeException(1, 'message', ServerRequestInterface::class, $message);
70: }
71:
72: /** @var array<string,mixed> */
73: $attributes = $message->getAttributes();
74:
75: return (new self(
76: $message->getMethod(),
77: $message->getUri(),
78: $message->getServerParams(),
79: $message->getBody(),
80: $message->getHeaders(),
81: $message->getRequestTarget(),
82: $message->getProtocolVersion(),
83: ))
84: ->withCookieParams($message->getCookieParams())
85: ->withQueryParams($message->getQueryParams())
86: ->withUploadedFiles($message->getUploadedFiles())
87: ->withParsedBody($message->getParsedBody())
88: ->with('Attributes', $attributes);
89: }
90:
91: /**
92: * @return mixed[]
93: */
94: public function getServerParams(): array
95: {
96: return $this->ServerParams;
97: }
98:
99: /**
100: * @return mixed[]
101: */
102: public function getCookieParams(): array
103: {
104: return $this->CookieParams;
105: }
106:
107: /**
108: * @return mixed[]
109: */
110: public function getQueryParams(): array
111: {
112: return $this->QueryParams;
113: }
114:
115: /**
116: * @return mixed[]
117: */
118: public function getUploadedFiles(): array
119: {
120: return $this->UploadedFiles;
121: }
122:
123: /**
124: * @return mixed[]|object|null
125: */
126: public function getParsedBody()
127: {
128: return $this->ParsedBody;
129: }
130:
131: /**
132: * @return array<string,mixed>
133: */
134: public function getAttributes(): array
135: {
136: return $this->Attributes;
137: }
138:
139: /**
140: * @return mixed
141: */
142: public function getAttribute(string $name, $default = null)
143: {
144: if (!array_key_exists($name, $this->Attributes)) {
145: return $default;
146: }
147: return $this->Attributes[$name];
148: }
149:
150: /**
151: * @param mixed[] $cookies
152: */
153: public function withCookieParams(array $cookies): ServerRequestInterface
154: {
155: return $this->with('CookieParams', $cookies);
156: }
157:
158: /**
159: * @param mixed[] $query
160: */
161: public function withQueryParams(array $query): ServerRequestInterface
162: {
163: return $this->with('QueryParams', $query);
164: }
165:
166: /**
167: * @param mixed[] $uploadedFiles
168: */
169: public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
170: {
171: return $this->with('UploadedFiles', $uploadedFiles);
172: }
173:
174: /**
175: * @param mixed[]|object|null $data
176: */
177: public function withParsedBody($data): ServerRequestInterface
178: {
179: return $this->with('ParsedBody', $this->filterParsedBody($data));
180: }
181:
182: /**
183: * @inheritDoc
184: */
185: public function withAttribute(string $name, $value): ServerRequestInterface
186: {
187: $attributes = $this->Attributes;
188: $attributes[$name] = $value;
189: return $this->with('Attributes', $attributes);
190: }
191:
192: /**
193: * @inheritDoc
194: */
195: public function withoutAttribute(string $name): ServerRequestInterface
196: {
197: $attributes = $this->Attributes;
198: unset($attributes[$name]);
199: return $this->with('Attributes', $attributes);
200: }
201:
202: /**
203: * @template T
204: *
205: * @param T $data
206: * @return T
207: */
208: private function filterParsedBody($data)
209: {
210: if ($data === null || is_array($data) || is_object($data)) {
211: return $data;
212: }
213: throw new InvalidArgumentTypeException(1, 'data', 'mixed[]|object|null', $data);
214: }
215: }
216: