RI websocket performance

This commit is contained in:
Fabian @ Blax Software 2026-01-24 14:49:51 +01:00
parent 2849f0fe5f
commit c76e6ae111
2 changed files with 34 additions and 40 deletions

View File

@ -59,12 +59,7 @@ class ControllerResolver
return self::$controllerCache[$eventPrefix]; return self::$controllerCache[$eventPrefix];
} }
// Ensure controllers are scanned // Try to find the controller (skip scanning in forked children - classes are already loaded)
if (!self::$scanned) {
self::scanControllers();
}
// Try to find the controller
$controllerClass = self::findController($eventPrefix); $controllerClass = self::findController($eventPrefix);
// Cache the result (even if null, to avoid repeated lookups) // Cache the result (even if null, to avoid repeated lookups)
@ -75,60 +70,58 @@ class ControllerResolver
/** /**
* Find controller using multiple strategies * Find controller using multiple strategies
* Optimized for speed: most common case (direct match) checked first
*/ */
private static function findController(string $eventPrefix): ?string private static function findController(string $eventPrefix): ?string
{ {
// Strategy 1: Direct match (e.g., 'app' → 'AppController') // Strategy 1: Direct match in app namespace (most common case)
// e.g., 'app' → '\App\Websocket\Controllers\AppController'
$directName = self::kebabToPascal($eventPrefix) . 'Controller'; $directName = self::kebabToPascal($eventPrefix) . 'Controller';
if ($class = self::findInAvailable($directName)) { $appClass = self::APP_NAMESPACE . $directName;
return $class;
// class_exists with autoload=true is fast for already-loaded classes
if (class_exists($appClass, true)) {
return $appClass;
} }
// Strategy 2: Folder structure (e.g., 'admin-user' → 'Admin/UserController') // Strategy 2: Direct match in vendor namespace
$vendorClass = self::VENDOR_NAMESPACE . $directName;
if (class_exists($vendorClass, true)) {
return $vendorClass;
}
// Strategy 3: Check pre-scanned available controllers (if scanned)
if (self::$scanned) {
if ($class = self::findInAvailable($directName)) {
return $class;
}
}
// Strategy 4: Folder structure (e.g., 'admin-user' → 'Admin/UserController')
// Only try this for kebab-case names with multiple parts
$parts = explode('-', $eventPrefix); $parts = explode('-', $eventPrefix);
if (count($parts) > 1) { if (count($parts) > 1) {
// Try progressively deeper folder structures
// 'admin-user-settings' tries:
// - Admin/User/SettingsController
// - Admin/UserSettingsController
// - AdminUser/SettingsController
for ($folderDepth = count($parts) - 1; $folderDepth >= 1; $folderDepth--) { for ($folderDepth = count($parts) - 1; $folderDepth >= 1; $folderDepth--) {
$folderParts = array_slice($parts, 0, $folderDepth); $folderParts = array_slice($parts, 0, $folderDepth);
$nameParts = array_slice($parts, $folderDepth); $nameParts = array_slice($parts, $folderDepth);
$folder = implode('/', array_map('ucfirst', $folderParts)); $folder = implode('\\', array_map('ucfirst', $folderParts));
$name = implode('', array_map('ucfirst', $nameParts)) . 'Controller'; $name = implode('', array_map('ucfirst', $nameParts)) . 'Controller';
// Try app namespace with folder // Try app namespace with folder
$appClass = self::APP_NAMESPACE . str_replace('/', '\\', $folder) . '\\' . $name; $appClass = self::APP_NAMESPACE . $folder . '\\' . $name;
if (class_exists($appClass)) { if (class_exists($appClass, true)) {
self::$availableControllers[strtolower($name)] = $appClass;
return $appClass; return $appClass;
} }
// Try vendor namespace with folder // Try vendor namespace with folder
$vendorClass = self::VENDOR_NAMESPACE . str_replace('/', '\\', $folder) . '\\' . $name; $vendorClass = self::VENDOR_NAMESPACE . $folder . '\\' . $name;
if (class_exists($vendorClass)) { if (class_exists($vendorClass, true)) {
self::$availableControllers[strtolower($name)] = $vendorClass;
return $vendorClass; return $vendorClass;
} }
} }
} }
// Strategy 3: Dynamic class_exists check (for newly added controllers)
$appClass = self::APP_NAMESPACE . $directName;
if (class_exists($appClass)) {
self::$availableControllers[strtolower($directName)] = $appClass;
return $appClass;
}
$vendorClass = self::VENDOR_NAMESPACE . $directName;
if (class_exists($vendorClass)) {
self::$availableControllers[strtolower($directName)] = $vendorClass;
return $vendorClass;
}
return null; return null;
} }

View File

@ -26,11 +26,12 @@ class ControllerResolverTest extends TestCase
/** @test */ /** @test */
public function it_caches_resolved_controllers() public function it_caches_resolved_controllers()
{ {
// First call scans and caches // First call resolves and caches
ControllerResolver::resolve('pusher'); ControllerResolver::resolve('pusher');
$stats = ControllerResolver::getStats(); $stats = ControllerResolver::getStats();
$this->assertTrue($stats['scanned']); // scanned may or may not be true (we no longer auto-scan on resolve)
// but cached should be > 0
$this->assertGreaterThan(0, $stats['cached']); $this->assertGreaterThan(0, $stats['cached']);
} }
@ -63,8 +64,8 @@ class ControllerResolverTest extends TestCase
/** @test */ /** @test */
public function it_clears_cache_correctly() public function it_clears_cache_correctly()
{ {
// Populate cache // Preload to ensure scanned is true
ControllerResolver::resolve('pusher'); ControllerResolver::preload();
$statsBefore = ControllerResolver::getStats(); $statsBefore = ControllerResolver::getStats();
$this->assertTrue($statsBefore['scanned']); $this->assertTrue($statsBefore['scanned']);