2
0
Web/Request.php

268 lines
6.9 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 parameter from route params, query params, or post data
*/
public function param(string $name): mixed
{
return $this->params[$name] ?? $this->queryParams[$name] ?? $this->postData[$name] ?? null;
}
/**
* input returns input value from any source (route, query, post, json)
*/
public function input(string $name, mixed $default = null): mixed
{
// Check route params first
if (isset($this->params[$name])) {
return $this->params[$name];
}
// Check query params
if (isset($this->queryParams[$name])) {
return $this->queryParams[$name];
}
// Check post data
if (isset($this->postData[$name])) {
return $this->postData[$name];
}
// Check JSON body
if ($this->contentType() === 'application/json') {
$json = $this->json();
if (is_array($json) && isset($json[$name])) {
return $json[$name];
}
}
return $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;
}
}