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:
2025-08-11 20:13:26 +02:00
parent 59fd3dd3b1
commit 55a330b223
3683 changed files with 2956207 additions and 16948 deletions

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent\Enums;
/**
* Enum representing different browser types
*/
enum BrowserType: string
{
case CHROME = 'chrome';
case FIREFOX = 'firefox';
case SAFARI = 'safari';
case EDGE = 'edge';
case OPERA = 'opera';
case INTERNET_EXPLORER = 'internet_explorer';
case SAMSUNG_BROWSER = 'samsung_browser';
case BRAVE = 'brave';
case VIVALDI = 'vivaldi';
case UNKNOWN = 'unknown';
/**
* Get human-readable name
*/
public function getDisplayName(): string
{
return match ($this) {
self::CHROME => 'Chrome',
self::FIREFOX => 'Firefox',
self::SAFARI => 'Safari',
self::EDGE => 'Edge',
self::OPERA => 'Opera',
self::INTERNET_EXPLORER => 'Internet Explorer',
self::SAMSUNG_BROWSER => 'Samsung Browser',
self::BRAVE => 'Brave',
self::VIVALDI => 'Vivaldi',
self::UNKNOWN => 'Unknown',
};
}
/**
* Get browser engine family
*/
public function getEngine(): EngineType
{
return match ($this) {
self::CHROME, self::EDGE, self::OPERA, self::SAMSUNG_BROWSER, self::BRAVE, self::VIVALDI => EngineType::BLINK,
self::SAFARI => EngineType::WEBKIT,
self::FIREFOX => EngineType::GECKO,
self::INTERNET_EXPLORER => EngineType::TRIDENT,
self::UNKNOWN => EngineType::UNKNOWN,
};
}
/**
* Check if browser is considered modern
*/
public function isModern(): bool
{
return match ($this) {
self::CHROME, self::FIREFOX, self::SAFARI, self::EDGE, self::OPERA,
self::SAMSUNG_BROWSER, self::BRAVE, self::VIVALDI => true,
self::INTERNET_EXPLORER, self::UNKNOWN => false,
};
}
/**
* Get minimum version considered modern for this browser
*/
public function getModernVersionThreshold(): string
{
return match ($this) {
self::CHROME => '60.0',
self::FIREFOX => '55.0',
self::SAFARI => '11.0',
self::EDGE => '79.0',
self::OPERA => '47.0',
self::SAMSUNG_BROWSER => '7.0',
self::BRAVE => '1.0',
self::VIVALDI => '2.0',
self::INTERNET_EXPLORER, self::UNKNOWN => '999.0', // Never modern
};
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent\Enums;
/**
* Enum representing different browser engine types
*/
enum EngineType: string
{
case BLINK = 'blink';
case WEBKIT = 'webkit';
case GECKO = 'gecko';
case TRIDENT = 'trident';
case EDGE_HTML = 'edge_html';
case UNKNOWN = 'unknown';
/**
* Get human-readable name
*/
public function getDisplayName(): string
{
return match ($this) {
self::BLINK => 'Blink',
self::WEBKIT => 'WebKit',
self::GECKO => 'Gecko',
self::TRIDENT => 'Trident',
self::EDGE_HTML => 'EdgeHTML',
self::UNKNOWN => 'Unknown',
};
}
/**
* Check if engine supports modern web standards
*/
public function isModern(): bool
{
return match ($this) {
self::BLINK, self::WEBKIT, self::GECKO => true,
self::TRIDENT, self::EDGE_HTML, self::UNKNOWN => false,
};
}
/**
* Get engine developer/maintainer
*/
public function getDeveloper(): string
{
return match ($this) {
self::BLINK => 'Google',
self::WEBKIT => 'Apple',
self::GECKO => 'Mozilla',
self::TRIDENT => 'Microsoft',
self::EDGE_HTML => 'Microsoft',
self::UNKNOWN => 'Unknown',
};
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent\Enums;
/**
* Enum representing different platform/operating system types
*/
enum PlatformType: string
{
case WINDOWS = 'windows';
case MACOS = 'macos';
case IOS = 'ios';
case ANDROID = 'android';
case LINUX = 'linux';
case CHROME_OS = 'chrome_os';
case UNKNOWN = 'unknown';
/**
* Get human-readable name
*/
public function getDisplayName(): string
{
return match ($this) {
self::WINDOWS => 'Windows',
self::MACOS => 'macOS',
self::IOS => 'iOS',
self::ANDROID => 'Android',
self::LINUX => 'Linux',
self::CHROME_OS => 'Chrome OS',
self::UNKNOWN => 'Unknown',
};
}
/**
* Check if platform is mobile
*/
public function isMobile(): bool
{
return match ($this) {
self::IOS, self::ANDROID => true,
self::WINDOWS, self::MACOS, self::LINUX, self::CHROME_OS, self::UNKNOWN => false,
};
}
/**
* Check if platform is desktop
*/
public function isDesktop(): bool
{
return match ($this) {
self::WINDOWS, self::MACOS, self::LINUX, self::CHROME_OS => true,
self::IOS, self::ANDROID, self::UNKNOWN => false,
};
}
/**
* Get platform family
*/
public function getFamily(): string
{
return match ($this) {
self::WINDOWS => 'Microsoft',
self::MACOS, self::IOS => 'Apple',
self::ANDROID => 'Google',
self::LINUX => 'Unix-like',
self::CHROME_OS => 'Google',
self::UNKNOWN => 'Unknown',
};
}
}

View File

@@ -0,0 +1,226 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent;
use App\Framework\UserAgent\Enums\BrowserType;
use App\Framework\UserAgent\Enums\EngineType;
use App\Framework\UserAgent\Enums\PlatformType;
/**
* Value Object representing a parsed User-Agent with rich metadata
* Immutable data structure optimized for business logic
*/
final readonly class ParsedUserAgent
{
public function __construct(
public string $raw,
public BrowserType $browser,
public string $browserVersion,
public PlatformType $platform,
public string $platformVersion,
public EngineType $engine,
public string $engineVersion,
public bool $isMobile,
public bool $isBot,
public bool $isModern
) {
}
/**
* Get original UserAgent object
*/
public function getUserAgent(): UserAgent
{
return new UserAgent($this->raw);
}
/**
* Get browser display name with version
*/
public function getBrowserName(): string
{
if ($this->browserVersion === 'Unknown') {
return $this->browser->getDisplayName();
}
return $this->browser->getDisplayName() . ' ' . $this->browserVersion;
}
/**
* Get platform display name with version
*/
public function getPlatformName(): string
{
if ($this->platformVersion === 'Unknown') {
return $this->platform->getDisplayName();
}
return $this->platform->getDisplayName() . ' ' . $this->platformVersion;
}
/**
* Get engine display name with version
*/
public function getEngineName(): string
{
if ($this->engineVersion === 'Unknown') {
return $this->engine->getDisplayName();
}
return $this->engine->getDisplayName() . ' ' . $this->engineVersion;
}
/**
* Get a comprehensive summary
*/
public function getSummary(): string
{
$parts = [$this->getBrowserName()];
$parts[] = 'on ' . $this->getPlatformName();
if ($this->isMobile) {
$parts[] = '(Mobile)';
}
if ($this->isBot) {
$parts[] = '(Bot)';
}
return implode(' ', $parts);
}
/**
* Check if browser supports specific web features
*/
public function supports(string $feature): bool
{
if (! $this->isModern || $this->isBot) {
return false;
}
return match ($feature) {
// Image formats
'webp' => $this->browser->getEngine() === EngineType::BLINK ||
($this->browser === BrowserType::FIREFOX && version_compare($this->browserVersion, '65.0', '>=')),
'avif' => $this->browser->getEngine() === EngineType::BLINK &&
version_compare($this->browserVersion, '85.0', '>='),
// JavaScript features
'es6' => $this->isModern,
'es2017' => $this->isModern && version_compare($this->browserVersion, $this->getEs2017MinVersion(), '>='),
'es2020' => $this->isModern && version_compare($this->browserVersion, $this->getEs2020MinVersion(), '>='),
// CSS features
'css-grid' => $this->isModern,
'css-flexbox' => $this->isModern,
'css-custom-properties' => $this->isModern,
// Web APIs
'service-worker' => $this->isModern && $this->platform !== PlatformType::IOS,
'web-push' => $this->isModern && $this->browser !== BrowserType::SAFARI,
'webrtc' => $this->isModern,
'websockets' => $this->isModern,
default => false
};
}
/**
* Get minimum browser version for ES2017 support
*/
private function getEs2017MinVersion(): string
{
return match ($this->browser) {
BrowserType::CHROME => '58.0',
BrowserType::FIREFOX => '52.0',
BrowserType::SAFARI => '10.1',
BrowserType::EDGE => '79.0',
BrowserType::OPERA => '45.0',
default => '999.0'
};
}
/**
* Get minimum browser version for ES2020 support
*/
private function getEs2020MinVersion(): string
{
return match ($this->browser) {
BrowserType::CHROME => '80.0',
BrowserType::FIREFOX => '72.0',
BrowserType::SAFARI => '13.1',
BrowserType::EDGE => '80.0',
BrowserType::OPERA => '67.0',
default => '999.0'
};
}
/**
* Get device category
*/
public function getDeviceCategory(): string
{
if ($this->isBot) {
return 'bot';
}
if ($this->platform->isMobile()) {
return 'mobile';
}
if ($this->platform->isDesktop()) {
return 'desktop';
}
return 'unknown';
}
/**
* Convert to array representation
* @return array<string, mixed>
*/
public function toArray(): array
{
return [
'raw' => $this->raw,
'browser' => [
'type' => $this->browser->value,
'name' => $this->browser->getDisplayName(),
'version' => $this->browserVersion,
'fullName' => $this->getBrowserName(),
],
'platform' => [
'type' => $this->platform->value,
'name' => $this->platform->getDisplayName(),
'version' => $this->platformVersion,
'fullName' => $this->getPlatformName(),
'family' => $this->platform->getFamily(),
],
'engine' => [
'type' => $this->engine->value,
'name' => $this->engine->getDisplayName(),
'version' => $this->engineVersion,
'fullName' => $this->getEngineName(),
'developer' => $this->engine->getDeveloper(),
],
'flags' => [
'isMobile' => $this->isMobile,
'isBot' => $this->isBot,
'isModern' => $this->isModern,
],
'deviceCategory' => $this->getDeviceCategory(),
'summary' => $this->getSummary(),
];
}
/**
* String representation
*/
public function __toString(): string
{
return $this->getSummary();
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent\Patterns;
use App\Framework\UserAgent\Enums\BrowserType;
/**
* Browser detection patterns optimized for modern browsers
*/
final readonly class BrowserPatterns
{
/**
* Get browser detection patterns in priority order
* Order matters! More specific patterns first
* @return array<array{pattern: string, browser: BrowserType, versionPattern: string}>
*/
public static function getPatterns(): array
{
return [
// Edge must be checked before Chrome (contains Chrome in UA)
[
'pattern' => '/Edg\/([\d.]+)/',
'browser' => BrowserType::EDGE,
'versionPattern' => '/Edg\/([\d.]+)/',
],
// Samsung Browser
[
'pattern' => '/SamsungBrowser\/([\d.]+)/',
'browser' => BrowserType::SAMSUNG_BROWSER,
'versionPattern' => '/SamsungBrowser\/([\d.]+)/',
],
// Brave Browser
[
'pattern' => '/Chrome\/.+\s+Brave\/([\d.]+)/',
'browser' => BrowserType::BRAVE,
'versionPattern' => '/Brave\/([\d.]+)/',
],
// Vivaldi
[
'pattern' => '/Vivaldi\/([\d.]+)/',
'browser' => BrowserType::VIVALDI,
'versionPattern' => '/Vivaldi\/([\d.]+)/',
],
// Opera (check before Chrome)
[
'pattern' => '/OPR\/([\d.]+)/',
'browser' => BrowserType::OPERA,
'versionPattern' => '/OPR\/([\d.]+)/',
],
// Chrome (must be after Edge, Opera, Samsung, etc.)
[
'pattern' => '/Chrome\/([\d.]+)/',
'browser' => BrowserType::CHROME,
'versionPattern' => '/Chrome\/([\d.]+)/',
],
// Firefox
[
'pattern' => '/Firefox\/([\d.]+)/',
'browser' => BrowserType::FIREFOX,
'versionPattern' => '/Firefox\/([\d.]+)/',
],
// Safari (check after Chrome-based browsers)
[
'pattern' => '/Version\/([\d.]+).*Safari\/([\d.]+)/',
'browser' => BrowserType::SAFARI,
'versionPattern' => '/Version\/([\d.]+)/',
],
// Internet Explorer 11
[
'pattern' => '/Trident\/7\.0.*rv:([\d.]+)/',
'browser' => BrowserType::INTERNET_EXPLORER,
'versionPattern' => '/rv:([\d.]+)/',
],
// Internet Explorer 6-10
[
'pattern' => '/MSIE\s+([\d.]+)/',
'browser' => BrowserType::INTERNET_EXPLORER,
'versionPattern' => '/MSIE\s+([\d.]+)/',
],
];
}
/**
* Get fallback patterns for edge cases
* @return array<array{pattern: string, browser: BrowserType}>
*/
public static function getFallbackPatterns(): array
{
return [
['pattern' => '/WebKit/', 'browser' => BrowserType::SAFARI],
['pattern' => '/Gecko/', 'browser' => BrowserType::FIREFOX],
['pattern' => '/Trident/', 'browser' => BrowserType::INTERNET_EXPLORER],
];
}
/**
* Patterns that indicate a bot/crawler (basic detection)
* @return string[]
*/
public static function getBotPatterns(): array
{
return [
'/bot/i',
'/crawler/i',
'/spider/i',
'/scraper/i',
'/curl/i',
'/wget/i',
'/python/i',
'/java/i',
'/php/i',
'/node/i',
'/axios/i',
'/postman/i',
'/insomnia/i',
];
}
/**
* Check if User-Agent indicates a bot
*/
public static function isBot(string $userAgent): bool
{
foreach (self::getBotPatterns() as $pattern) {
if (preg_match($pattern, $userAgent)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent\Patterns;
use App\Framework\UserAgent\Enums\EngineType;
/**
* Browser engine detection patterns
*/
final readonly class EnginePatterns
{
/**
* Get engine detection patterns in priority order
* @return array<array{pattern: string, engine: EngineType, versionPattern: string}>
*/
public static function getPatterns(): array
{
return [
// Blink (Chrome 28+, Opera 15+, Edge 79+)
[
'pattern' => '/Chrome\/\d+.*Safari\/\d+/',
'engine' => EngineType::BLINK,
'versionPattern' => '/Chrome\/([\d.]+)/', // Use Chrome version as proxy
],
// WebKit (Safari, older Chrome)
[
'pattern' => '/AppleWebKit\/([\d.]+).*Version\/[\d.]+.*Safari/',
'engine' => EngineType::WEBKIT,
'versionPattern' => '/AppleWebKit\/([\d.]+)/',
],
// Gecko (Firefox)
[
'pattern' => '/Gecko\/([\d]+).*Firefox/',
'engine' => EngineType::GECKO,
'versionPattern' => '/Gecko\/([\d]+)/',
],
// Trident (Internet Explorer)
[
'pattern' => '/Trident\/([\d.]+)/',
'engine' => EngineType::TRIDENT,
'versionPattern' => '/Trident\/([\d.]+)/',
],
// EdgeHTML (Legacy Edge 12-18)
[
'pattern' => '/Edge\/([\d.]+)/',
'engine' => EngineType::EDGE_HTML,
'versionPattern' => '/Edge\/([\d.]+)/',
],
];
}
/**
* Get fallback engine detection based on browser
* @return array<string, EngineType>
*/
public static function getBrowserEngineMap(): array
{
return [
'chrome' => EngineType::BLINK,
'edge' => EngineType::BLINK,
'opera' => EngineType::BLINK,
'samsung_browser' => EngineType::BLINK,
'brave' => EngineType::BLINK,
'vivaldi' => EngineType::BLINK,
'safari' => EngineType::WEBKIT,
'firefox' => EngineType::GECKO,
'internet_explorer' => EngineType::TRIDENT,
];
}
/**
* Map Gecko build numbers to readable versions
* @return array<string, string>
*/
public static function getGeckoVersionMap(): array
{
return [
'20100101' => '2.0', // Firefox 4+
'20090715' => '1.9.1', // Firefox 3.5
'20081217' => '1.9.0', // Firefox 3.0
];
}
/**
* Convert Gecko build number to version
*/
public static function formatGeckoVersion(string $buildNumber): string
{
$map = self::getGeckoVersionMap();
// Try exact match first
if (isset($map[$buildNumber])) {
return $map[$buildNumber];
}
// For modern Firefox, use a simplified approach
if ((int) $buildNumber >= 20100101) {
return '2.0+';
}
return $buildNumber;
}
/**
* Get minimum engine versions for modern web features
* @return array<string, array<string, string>>
*/
public static function getFeatureSupport(): array
{
return [
'css-grid' => [
'blink' => '57.0',
'webkit' => '10.1',
'gecko' => '52.0',
],
'es6' => [
'blink' => '51.0',
'webkit' => '10.0',
'gecko' => '45.0',
],
'service-worker' => [
'blink' => '40.0',
'webkit' => '11.1',
'gecko' => '44.0',
],
];
}
}

View File

@@ -0,0 +1,147 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent\Patterns;
use App\Framework\UserAgent\Enums\PlatformType;
/**
* Platform/OS detection patterns
*/
final readonly class PlatformPatterns
{
/**
* Get platform detection patterns in priority order
* @return array<array{pattern: string, platform: PlatformType, versionPattern: string}>
*/
public static function getPatterns(): array
{
return [
// iOS (check before macOS as iOS UA contains Mac OS X)
[
'pattern' => '/iPhone OS ([\d_]+)/',
'platform' => PlatformType::IOS,
'versionPattern' => '/iPhone OS ([\d_]+)/',
],
[
'pattern' => '/OS ([\d_]+) like Mac OS X/',
'platform' => PlatformType::IOS,
'versionPattern' => '/OS ([\d_]+) like Mac OS X/',
],
// Android
[
'pattern' => '/Android ([\d.]+)/',
'platform' => PlatformType::ANDROID,
'versionPattern' => '/Android ([\d.]+)/',
],
// Windows
[
'pattern' => '/Windows NT ([\d.]+)/',
'platform' => PlatformType::WINDOWS,
'versionPattern' => '/Windows NT ([\d.]+)/',
],
// macOS (after iOS check)
[
'pattern' => '/Mac OS X ([\d_]+)/',
'platform' => PlatformType::MACOS,
'versionPattern' => '/Mac OS X ([\d_]+)/',
],
[
'pattern' => '/Macintosh.*OS X ([\d_]+)/',
'platform' => PlatformType::MACOS,
'versionPattern' => '/OS X ([\d_]+)/',
],
// Chrome OS
[
'pattern' => '/CrOS \w+ ([\d.]+)/',
'platform' => PlatformType::CHROME_OS,
'versionPattern' => '/CrOS \w+ ([\d.]+)/',
],
// Linux (generic, should be last)
[
'pattern' => '/Linux/',
'platform' => PlatformType::LINUX,
'versionPattern' => '//', // No version pattern for generic Linux
],
];
}
/**
* Map Windows NT versions to readable names
* @return array<string, string>
*/
public static function getWindowsVersionMap(): array
{
return [
'10.0' => '10/11',
'6.3' => '8.1',
'6.2' => '8',
'6.1' => '7',
'6.0' => 'Vista',
'5.2' => 'XP 64-bit',
'5.1' => 'XP',
'5.0' => '2000',
];
}
/**
* Convert Windows NT version to readable format
*/
public static function formatWindowsVersion(string $version): string
{
$map = self::getWindowsVersionMap();
return $map[$version] ?? $version;
}
/**
* Convert iOS/macOS version from underscore to dot notation
*/
public static function formatAppleVersion(string $version): string
{
return str_replace('_', '.', $version);
}
/**
* Patterns that indicate mobile devices
* @return string[]
*/
public static function getMobilePatterns(): array
{
return [
'/Mobile/',
'/Android/',
'/iPhone/',
'/iPad/',
'/iPod/',
'/BlackBerry/',
'/Windows Phone/',
'/Opera Mini/',
'/webOS/',
'/Kindle/',
'/Silk/',
'/Mobile Safari/',
'/SamsungBrowser/',
];
}
/**
* Check if User-Agent indicates a mobile device
*/
public static function isMobile(string $userAgent): bool
{
foreach (self::getMobilePatterns() as $pattern) {
if (preg_match($pattern, $userAgent)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\Framework\UserAgent;
/**
* Value Object representing an unparsed User-Agent string
* Lightweight, suitable for session storage
*/
final readonly class UserAgent
{
private function __construct(
public string $value
) {
}
/**
* Factory method - create from string
*/
public static function fromString(string $userAgentString): self
{
return new self($userAgentString);
}
/**
* Parse User-Agent into a rich ParsedUserAgent object
* Note: Consider injecting UserAgentParser via DI container for better performance
*/
public function parse(): ParsedUserAgent
{
return new UserAgentParser()->parse($this->value);
}
/**
* String representation
*/
public function __toString(): string
{
return $this->value;
}
}

View 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()),
];
}
}