major update
This commit is contained in:
226
lib/Http/RequestContext.php
Normal file
226
lib/Http/RequestContext.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Http;
|
||||
|
||||
use MintyPHP\Repository\Support\RepoQuery;
|
||||
|
||||
final class RequestContext
|
||||
{
|
||||
/** @var array<string, mixed>|null */
|
||||
private static ?array $context = null;
|
||||
|
||||
public static function start(array $overrides = []): void
|
||||
{
|
||||
if (self::$context === null) {
|
||||
self::$context = self::buildDefaultContext();
|
||||
}
|
||||
|
||||
self::applyOverrides($overrides);
|
||||
}
|
||||
|
||||
public static function ensureStarted(): void
|
||||
{
|
||||
if (self::$context !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$context = self::buildDefaultContext();
|
||||
}
|
||||
|
||||
public static function id(): string
|
||||
{
|
||||
self::ensureStarted();
|
||||
$requestId = trim((string) (self::$context['request_id'] ?? ''));
|
||||
if ($requestId === '') {
|
||||
$requestId = RepoQuery::uuidV4();
|
||||
self::$context['request_id'] = $requestId;
|
||||
}
|
||||
return $requestId;
|
||||
}
|
||||
|
||||
public static function currentId(): ?string
|
||||
{
|
||||
if (self::$context === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$requestId = trim((string) (self::$context['request_id'] ?? ''));
|
||||
return $requestId !== '' ? $requestId : null;
|
||||
}
|
||||
|
||||
public static function setChannel(string $channel): void
|
||||
{
|
||||
self::ensureStarted();
|
||||
$normalized = self::normalizeChannel($channel);
|
||||
if ($normalized !== '') {
|
||||
self::$context['channel'] = $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
public static function setPath(string $path): void
|
||||
{
|
||||
self::ensureStarted();
|
||||
$path = trim($path);
|
||||
if ($path === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$context['path'] = substr($path, 0, 255);
|
||||
}
|
||||
|
||||
public static function context(): array
|
||||
{
|
||||
self::ensureStarted();
|
||||
|
||||
return self::$context ?? [];
|
||||
}
|
||||
|
||||
public static function requestHeaderValue(): string
|
||||
{
|
||||
return self::id();
|
||||
}
|
||||
|
||||
public static function hashValue(string $value): string
|
||||
{
|
||||
$secret = defined('APP_CRYPTO_KEY') ? trim((string) APP_CRYPTO_KEY) : '';
|
||||
if ($secret === '') {
|
||||
return hash('sha256', $value);
|
||||
}
|
||||
|
||||
return hash_hmac('sha256', $value, $secret);
|
||||
}
|
||||
|
||||
public static function resetForTests(): void
|
||||
{
|
||||
self::$context = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function buildDefaultContext(): array
|
||||
{
|
||||
$method = strtoupper(trim((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')));
|
||||
if ($method === '') {
|
||||
$method = PHP_SAPI === 'cli' ? 'CLI' : 'GET';
|
||||
}
|
||||
|
||||
$uri = (string) ($_SERVER['REQUEST_URI'] ?? '');
|
||||
$path = parse_url($uri, PHP_URL_PATH);
|
||||
$path = is_string($path) ? trim($path) : '';
|
||||
if ($path === '' && PHP_SAPI === 'cli') {
|
||||
$path = (string) ($_SERVER['argv'][0] ?? 'cli');
|
||||
}
|
||||
|
||||
$requestId = self::requestIdFromHeader();
|
||||
if ($requestId === null) {
|
||||
$requestId = RepoQuery::uuidV4();
|
||||
}
|
||||
|
||||
return [
|
||||
'request_id' => $requestId,
|
||||
'channel' => self::detectChannel($path),
|
||||
'method' => substr($method, 0, 8),
|
||||
'path' => substr($path, 0, 255),
|
||||
'ip' => self::normalizeIp((string) ($_SERVER['REMOTE_ADDR'] ?? '')),
|
||||
'user_agent' => self::normalizeUserAgent((string) ($_SERVER['HTTP_USER_AGENT'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
private static function applyOverrides(array $overrides): void
|
||||
{
|
||||
foreach ($overrides as $key => $value) {
|
||||
if (!is_string($key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === 'request_id') {
|
||||
$requestId = trim((string) $value);
|
||||
if (self::isValidUuid($requestId)) {
|
||||
self::$context['request_id'] = $requestId;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === 'channel') {
|
||||
$channel = self::normalizeChannel((string) $value);
|
||||
if ($channel !== '') {
|
||||
self::$context['channel'] = $channel;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === 'method') {
|
||||
$method = strtoupper(trim((string) $value));
|
||||
if ($method !== '') {
|
||||
self::$context['method'] = substr($method, 0, 8);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === 'path') {
|
||||
$path = trim((string) $value);
|
||||
if ($path !== '') {
|
||||
self::$context['path'] = substr($path, 0, 255);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function detectChannel(string $path): string
|
||||
{
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return 'cli';
|
||||
}
|
||||
if (defined('MINTY_API_REQUEST')) {
|
||||
return 'api';
|
||||
}
|
||||
|
||||
$normalizedPath = ltrim(strtolower(trim($path)), '/');
|
||||
if (str_starts_with($normalizedPath, 'api/')) {
|
||||
return 'api';
|
||||
}
|
||||
|
||||
return 'web';
|
||||
}
|
||||
|
||||
private static function normalizeChannel(string $channel): string
|
||||
{
|
||||
$channel = strtolower(trim($channel));
|
||||
return in_array($channel, ['web', 'api', 'scheduler', 'cli'], true) ? $channel : '';
|
||||
}
|
||||
|
||||
private static function requestIdFromHeader(): ?string
|
||||
{
|
||||
$header = trim((string) ($_SERVER['HTTP_X_REQUEST_ID'] ?? ''));
|
||||
if (self::isValidUuid($header)) {
|
||||
return strtolower($header);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function isValidUuid(string $value): bool
|
||||
{
|
||||
return preg_match('/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i', $value) === 1;
|
||||
}
|
||||
|
||||
private static function normalizeIp(string $ip): string
|
||||
{
|
||||
$ip = trim($ip);
|
||||
if ($ip === '') {
|
||||
return '';
|
||||
}
|
||||
return substr($ip, 0, 45);
|
||||
}
|
||||
|
||||
private static function normalizeUserAgent(string $userAgent): string
|
||||
{
|
||||
$userAgent = trim($userAgent);
|
||||
if ($userAgent === '') {
|
||||
return '';
|
||||
}
|
||||
return substr($userAgent, 0, 255);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user