Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
254
src/Framework/UserAgent/UserAgentParser.php
Normal file
254
src/Framework/UserAgent/UserAgentParser.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\UserAgent;
|
||||
|
||||
use App\Framework\Cache\Cache;
|
||||
use App\Framework\UserAgent\Enums\BrowserType;
|
||||
use App\Framework\UserAgent\Enums\EngineType;
|
||||
use App\Framework\UserAgent\Enums\PlatformType;
|
||||
use App\Framework\UserAgent\Patterns\BrowserPatterns;
|
||||
use App\Framework\UserAgent\Patterns\EnginePatterns;
|
||||
use App\Framework\UserAgent\Patterns\PlatformPatterns;
|
||||
|
||||
/**
|
||||
* High-performance User-Agent parser with caching
|
||||
* Optimized for modern browsers and frameworks
|
||||
*/
|
||||
final class UserAgentParser
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ?Cache $cache = null
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse User-Agent string into structured ParsedUserAgent object
|
||||
*/
|
||||
public function parse(string $userAgentString): ParsedUserAgent
|
||||
{
|
||||
$normalized = trim($userAgentString);
|
||||
|
||||
// Handle empty User-Agent
|
||||
if ($normalized === '') {
|
||||
return $this->createUnknownUserAgent('');
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
$cacheKey = 'useragent:' . md5($normalized);
|
||||
if ($this->cache) {
|
||||
$cached = $this->cache->get($cacheKey);
|
||||
if ($cached instanceof ParsedUserAgent) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse components
|
||||
$browser = $this->parseBrowser($normalized);
|
||||
$browserVersion = $this->parseBrowserVersion($normalized, $browser);
|
||||
$platform = $this->parsePlatform($normalized);
|
||||
$platformVersion = $this->parsePlatformVersion($normalized, $platform);
|
||||
$engine = $this->parseEngine($normalized, $browser);
|
||||
$engineVersion = $this->parseEngineVersion($normalized, $engine);
|
||||
$isMobile = PlatformPatterns::isMobile($normalized) || $platform->isMobile();
|
||||
$isBot = BrowserPatterns::isBot($normalized);
|
||||
$isModern = $this->determineModernBrowser($browser, $browserVersion, $isBot);
|
||||
|
||||
$parsedUserAgent = new ParsedUserAgent(
|
||||
raw: $normalized,
|
||||
browser: $browser,
|
||||
browserVersion: $browserVersion,
|
||||
platform: $platform,
|
||||
platformVersion: $platformVersion,
|
||||
engine: $engine,
|
||||
engineVersion: $engineVersion,
|
||||
isMobile: $isMobile,
|
||||
isBot: $isBot,
|
||||
isModern: $isModern
|
||||
);
|
||||
|
||||
// Cache result
|
||||
if ($this->cache) {
|
||||
$this->cache->set($cacheKey, $parsedUserAgent, 3600); // Cache for 1 hour
|
||||
}
|
||||
|
||||
return $parsedUserAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse browser type and version
|
||||
*/
|
||||
private function parseBrowser(string $userAgent): BrowserType
|
||||
{
|
||||
foreach (BrowserPatterns::getPatterns() as $pattern) {
|
||||
if (preg_match($pattern['pattern'], $userAgent)) {
|
||||
return $pattern['browser'];
|
||||
}
|
||||
}
|
||||
|
||||
// Try fallback patterns
|
||||
foreach (BrowserPatterns::getFallbackPatterns() as $fallback) {
|
||||
if (preg_match($fallback['pattern'], $userAgent)) {
|
||||
return $fallback['browser'];
|
||||
}
|
||||
}
|
||||
|
||||
return BrowserType::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse browser version
|
||||
*/
|
||||
private function parseBrowserVersion(string $userAgent, BrowserType $browser): string
|
||||
{
|
||||
// Find matching pattern for this browser
|
||||
foreach (BrowserPatterns::getPatterns() as $pattern) {
|
||||
if ($pattern['browser'] === $browser && preg_match($pattern['versionPattern'], $userAgent, $matches)) {
|
||||
return $matches[1] ?? 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse platform/operating system
|
||||
*/
|
||||
private function parsePlatform(string $userAgent): PlatformType
|
||||
{
|
||||
foreach (PlatformPatterns::getPatterns() as $pattern) {
|
||||
if (preg_match($pattern['pattern'], $userAgent)) {
|
||||
return $pattern['platform'];
|
||||
}
|
||||
}
|
||||
|
||||
return PlatformType::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse platform version
|
||||
*/
|
||||
private function parsePlatformVersion(string $userAgent, PlatformType $platform): string
|
||||
{
|
||||
foreach (PlatformPatterns::getPatterns() as $pattern) {
|
||||
if ($pattern['platform'] === $platform &&
|
||||
! empty($pattern['versionPattern']) &&
|
||||
preg_match($pattern['versionPattern'], $userAgent, $matches)) {
|
||||
|
||||
$version = $matches[1] ?? 'Unknown';
|
||||
|
||||
// Format version based on platform
|
||||
return match ($platform) {
|
||||
PlatformType::WINDOWS => PlatformPatterns::formatWindowsVersion($version),
|
||||
PlatformType::MACOS, PlatformType::IOS => PlatformPatterns::formatAppleVersion($version),
|
||||
default => $version
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse browser engine
|
||||
*/
|
||||
private function parseEngine(string $userAgent, BrowserType $browser): EngineType
|
||||
{
|
||||
// Try pattern-based detection first
|
||||
foreach (EnginePatterns::getPatterns() as $pattern) {
|
||||
if (preg_match($pattern['pattern'], $userAgent)) {
|
||||
return $pattern['engine'];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to browser-based mapping
|
||||
$engineMap = EnginePatterns::getBrowserEngineMap();
|
||||
|
||||
return $engineMap[$browser->value] ?? EngineType::UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse engine version
|
||||
*/
|
||||
private function parseEngineVersion(string $userAgent, EngineType $engine): string
|
||||
{
|
||||
foreach (EnginePatterns::getPatterns() as $pattern) {
|
||||
if ($pattern['engine'] === $engine && preg_match($pattern['versionPattern'], $userAgent, $matches)) {
|
||||
$version = $matches[1] ?? 'Unknown';
|
||||
|
||||
// Special formatting for Gecko
|
||||
if ($engine === EngineType::GECKO) {
|
||||
return EnginePatterns::formatGeckoVersion($version);
|
||||
}
|
||||
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if browser is considered modern
|
||||
*/
|
||||
private function determineModernBrowser(BrowserType $browser, string $version, bool $isBot): bool
|
||||
{
|
||||
if ($isBot || $version === 'Unknown') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $browser->isModern()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$threshold = $browser->getModernVersionThreshold();
|
||||
|
||||
return version_compare($version, $threshold, '>=');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create unknown ParsedUserAgent object
|
||||
*/
|
||||
private function createUnknownUserAgent(string $raw): ParsedUserAgent
|
||||
{
|
||||
return new ParsedUserAgent(
|
||||
raw: $raw,
|
||||
browser: BrowserType::UNKNOWN,
|
||||
browserVersion: 'Unknown',
|
||||
platform: PlatformType::UNKNOWN,
|
||||
platformVersion: 'Unknown',
|
||||
engine: EngineType::UNKNOWN,
|
||||
engineVersion: 'Unknown',
|
||||
isMobile: false,
|
||||
isBot: false,
|
||||
isModern: false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear parser cache
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
if ($this->cache) {
|
||||
// Clear all useragent cache entries
|
||||
// This would need cache implementation that supports key patterns
|
||||
// For now, this is a placeholder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parser statistics (for debugging/monitoring)
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getStats(): array
|
||||
{
|
||||
return [
|
||||
'cacheEnabled' => $this->cache !== null,
|
||||
'supportedBrowsers' => count(BrowserType::cases()),
|
||||
'supportedPlatforms' => count(PlatformType::cases()),
|
||||
'supportedEngines' => count(EngineType::cases()),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user