- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
255 lines
7.6 KiB
PHP
255 lines
7.6 KiB
PHP
<?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()),
|
|
];
|
|
}
|
|
}
|