270 lines
6.8 KiB
PHP
270 lines
6.8 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Request represents an incoming HTTP request
|
|
*/
|
|
class Request
|
|
{
|
|
public string $method;
|
|
public string $uri;
|
|
public string $path;
|
|
public string $query;
|
|
public array $headers;
|
|
public string $body;
|
|
public array $params = [];
|
|
public array $queryParams = [];
|
|
public array $postData = [];
|
|
public array $cookies;
|
|
|
|
/**
|
|
* __construct creates a new Request from PHP globals
|
|
*/
|
|
public function __construct()
|
|
{
|
|
$this->method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
|
$this->uri = $_SERVER['REQUEST_URI'] ?? '/';
|
|
|
|
$urlParts = parse_url($this->uri);
|
|
$this->path = $urlParts['path'] ?? '/';
|
|
$this->query = $urlParts['query'] ?? '';
|
|
|
|
parse_str($this->query, $this->queryParams);
|
|
|
|
$this->headers = $this->parseHeaders();
|
|
$this->body = file_get_contents('php://input') ?: '';
|
|
$this->cookies = $_COOKIE ?? [];
|
|
|
|
if ($this->method === 'POST' && $this->contentType() === 'application/x-www-form-urlencoded') {
|
|
parse_str($this->body, $this->postData);
|
|
} elseif ($this->method === 'POST' && str_contains($this->contentType(), 'multipart/form-data')) {
|
|
$this->postData = $_POST ?? [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* parseHeaders extracts HTTP headers from $_SERVER
|
|
*/
|
|
private function parseHeaders(): array
|
|
{
|
|
$headers = [];
|
|
foreach ($_SERVER as $key => $value) {
|
|
if (str_starts_with($key, 'HTTP_')) {
|
|
$header = str_replace('_', '-', substr($key, 5));
|
|
$headers[strtolower($header)] = $value;
|
|
}
|
|
}
|
|
|
|
if (isset($_SERVER['CONTENT_TYPE'])) $headers['content-type'] = $_SERVER['CONTENT_TYPE'];
|
|
if (isset($_SERVER['CONTENT_LENGTH'])) $headers['content-length'] = $_SERVER['CONTENT_LENGTH'];
|
|
|
|
return $headers;
|
|
}
|
|
|
|
/**
|
|
* header returns the value of the specified header
|
|
*/
|
|
public function header(string $name): ?string
|
|
{
|
|
return $this->headers[strtolower($name)] ?? null;
|
|
}
|
|
|
|
/**
|
|
* contentType returns the content type without charset
|
|
*/
|
|
public function contentType(): string
|
|
{
|
|
$contentType = $this->header('content-type') ?? '';
|
|
return explode(';', $contentType)[0];
|
|
}
|
|
|
|
/**
|
|
* json decodes the request body as JSON
|
|
*/
|
|
public function json(): mixed
|
|
{
|
|
return json_decode($this->body, true);
|
|
}
|
|
|
|
/**
|
|
* cookie returns the value of the specified cookie
|
|
*/
|
|
public function cookie(string $name): ?string
|
|
{
|
|
return $this->cookies[$name] ?? null;
|
|
}
|
|
|
|
/**
|
|
* param returns a route parameter by integer index
|
|
*/
|
|
public function param(int $index, mixed $default = null): mixed
|
|
{
|
|
return $this->params[$index] ?? $default;
|
|
}
|
|
|
|
/**
|
|
* input returns a form input value (POST data)
|
|
*/
|
|
public function input(string $name, mixed $default = null): mixed
|
|
{
|
|
return $this->postData[$name] ?? $default;
|
|
}
|
|
|
|
/**
|
|
* query returns a query parameter value
|
|
*/
|
|
public function query(string $name, mixed $default = null): mixed
|
|
{
|
|
return $this->queryParams[$name] ?? $default;
|
|
}
|
|
|
|
/**
|
|
* jsonValue returns a value from JSON body
|
|
*/
|
|
public function jsonValue(string $name, mixed $default = null): mixed
|
|
{
|
|
if ($this->contentType() !== 'application/json') {
|
|
return $default;
|
|
}
|
|
|
|
$json = $this->json();
|
|
if (!is_array($json)) {
|
|
return $default;
|
|
}
|
|
|
|
return $json[$name] ?? $default;
|
|
}
|
|
|
|
/**
|
|
* all returns all input data merged from all sources
|
|
*/
|
|
public function all(): array
|
|
{
|
|
$data = array_merge($this->queryParams, $this->postData, $this->params);
|
|
|
|
if ($this->contentType() === 'application/json') {
|
|
$json = $this->json();
|
|
if (is_array($json)) {
|
|
$data = array_merge($data, $json);
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* only returns only specified keys from input
|
|
*/
|
|
public function only(array $keys): array
|
|
{
|
|
$all = $this->all();
|
|
return array_intersect_key($all, array_flip($keys));
|
|
}
|
|
|
|
/**
|
|
* except returns all input except specified keys
|
|
*/
|
|
public function except(array $keys): array
|
|
{
|
|
$all = $this->all();
|
|
return array_diff_key($all, array_flip($keys));
|
|
}
|
|
|
|
/**
|
|
* has checks if input key exists
|
|
*/
|
|
public function has(string $key): bool
|
|
{
|
|
return $this->input($key) !== null;
|
|
}
|
|
|
|
/**
|
|
* expectsJson checks if request expects JSON response
|
|
*/
|
|
public function expectsJson(): bool
|
|
{
|
|
$accept = $this->header('accept') ?? '';
|
|
return str_contains($accept, 'application/json') ||
|
|
str_contains($accept, 'text/json') ||
|
|
$this->header('x-requested-with') === 'XMLHttpRequest';
|
|
}
|
|
|
|
/**
|
|
* isAjax checks if request is AJAX
|
|
*/
|
|
public function isAjax(): bool
|
|
{
|
|
return $this->header('x-requested-with') === 'XMLHttpRequest';
|
|
}
|
|
|
|
/**
|
|
* ip returns the client IP address
|
|
*/
|
|
public function ip(): string
|
|
{
|
|
// Check for proxied IPs
|
|
if ($ip = $this->header('x-forwarded-for')) {
|
|
return explode(',', $ip)[0];
|
|
}
|
|
|
|
if ($ip = $this->header('x-real-ip')) {
|
|
return $ip;
|
|
}
|
|
|
|
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
|
}
|
|
|
|
/**
|
|
* userAgent returns the user agent string
|
|
*/
|
|
public function userAgent(): string
|
|
{
|
|
return $this->header('user-agent') ?? '';
|
|
}
|
|
|
|
/**
|
|
* referer returns the referer URL
|
|
*/
|
|
public function referer(): ?string
|
|
{
|
|
return $this->header('referer');
|
|
}
|
|
|
|
/**
|
|
* isSecure checks if request is over HTTPS
|
|
*/
|
|
public function isSecure(): bool
|
|
{
|
|
return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
|
|
($_SERVER['SERVER_PORT'] ?? 80) == 443 ||
|
|
($this->header('x-forwarded-proto') === 'https');
|
|
}
|
|
|
|
/**
|
|
* url returns the full URL of the request
|
|
*/
|
|
public function url(): string
|
|
{
|
|
$protocol = $this->isSecure() ? 'https' : 'http';
|
|
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
|
return $protocol . '://' . $host . $this->uri;
|
|
}
|
|
|
|
/**
|
|
* fullUrl returns the URL with query string
|
|
*/
|
|
public function fullUrl(): string
|
|
{
|
|
return $this->url();
|
|
}
|
|
|
|
/**
|
|
* is checks if the request path matches a pattern
|
|
*/
|
|
public function is(string $pattern): bool
|
|
{
|
|
$pattern = preg_quote($pattern, '#');
|
|
$pattern = str_replace('\*', '.*', $pattern);
|
|
return preg_match('#^' . $pattern . '$#', $this->path) === 1;
|
|
}
|
|
}
|