RI websocket performance
This commit is contained in:
parent
5b8a2a8112
commit
2849f0fe5f
|
|
@ -251,11 +251,6 @@ class StartServer extends Command
|
||||||
$restartData = $this->getLastRestartData();
|
$restartData = $this->getLastRestartData();
|
||||||
$currentRestart = $restartData['time'] ?? null;
|
$currentRestart = $restartData['time'] ?? null;
|
||||||
|
|
||||||
\Log::channel('websocket')->debug('Restart timer tick', [
|
|
||||||
'last_restart' => $this->lastRestart,
|
|
||||||
'current_restart' => $currentRestart,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Only trigger restart if lastRestart was set and a new restart signal was received
|
// Only trigger restart if lastRestart was set and a new restart signal was received
|
||||||
if ($this->lastRestart !== null && $currentRestart !== null && $currentRestart !== $this->lastRestart) {
|
if ($this->lastRestart !== null && $currentRestart !== null && $currentRestart !== $this->lastRestart) {
|
||||||
$this->restartSoftShutdown = $restartData['soft'] ?? false;
|
$this->restartSoftShutdown = $restartData['soft'] ?? false;
|
||||||
|
|
@ -402,10 +397,88 @@ class StartServer extends Command
|
||||||
$this->buildServer();
|
$this->buildServer();
|
||||||
\Log::channel('websocket')->debug('Server instance built, starting event loop...');
|
\Log::channel('websocket')->debug('Server instance built, starting event loop...');
|
||||||
|
|
||||||
|
// Warmup: pre-heat hot code paths to trigger JIT compilation
|
||||||
|
// This reduces first-request latency from ~15ms to ~3ms
|
||||||
|
$this->warmupCodePaths();
|
||||||
|
|
||||||
$this->server->run();
|
$this->server->run();
|
||||||
\Log::channel('websocket')->debug('Event loop stopped, server shutdown complete');
|
\Log::channel('websocket')->debug('Event loop stopped, server shutdown complete');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-heat frequently used code paths to trigger JIT compilation
|
||||||
|
* and load code into CPU cache before real requests arrive.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function warmupCodePaths(): void
|
||||||
|
{
|
||||||
|
// Warmup JSON encode/decode (used in every message)
|
||||||
|
$testPayload = '{"event":"pusher.ping","data":{}}';
|
||||||
|
for ($i = 0; $i < 10; $i++) {
|
||||||
|
$decoded = json_decode($testPayload, true);
|
||||||
|
json_encode($decoded);
|
||||||
|
strpos($testPayload, 'ping');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warmup time() function
|
||||||
|
time();
|
||||||
|
|
||||||
|
// Warmup string operations
|
||||||
|
$pong = '{"event":"pusher.pong"}';
|
||||||
|
strlen($pong);
|
||||||
|
|
||||||
|
// Preload WebSocket controller classes to avoid autoloading latency on first request
|
||||||
|
$this->preloadControllers();
|
||||||
|
|
||||||
|
// Warmup database connection (will be re-established in child, but PDO classes get loaded)
|
||||||
|
try {
|
||||||
|
\DB::connection()->getPdo();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// Ignore connection errors, we just want to load classes
|
||||||
|
}
|
||||||
|
|
||||||
|
\Log::channel('websocket')->debug('Code paths warmed up (JIT pre-compiled)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preload all WebSocket controller classes to avoid autoloading latency
|
||||||
|
* on first request. This triggers class loading and JIT compilation.
|
||||||
|
*
|
||||||
|
* Uses ControllerResolver which:
|
||||||
|
* - Scans app/Websocket/Controllers/ recursively (including subfolders)
|
||||||
|
* - Caches discovered controllers in memory for O(1) lookup
|
||||||
|
* - Supports folder-based controllers (e.g., Admin/UserController)
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function preloadControllers(): void
|
||||||
|
{
|
||||||
|
// Use ControllerResolver to scan and cache all controllers
|
||||||
|
\BlaxSoftware\LaravelWebSockets\Websocket\ControllerResolver::preload();
|
||||||
|
|
||||||
|
$stats = \BlaxSoftware\LaravelWebSockets\Websocket\ControllerResolver::getStats();
|
||||||
|
\Log::channel('websocket')->debug('Controllers preloaded', [
|
||||||
|
'available' => $stats['available'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Preload commonly used classes
|
||||||
|
$commonClasses = [
|
||||||
|
\BlaxSoftware\LaravelWebSockets\Websocket\Controller::class,
|
||||||
|
\BlaxSoftware\LaravelWebSockets\Websocket\ControllerResolver::class,
|
||||||
|
\BlaxSoftware\LaravelWebSockets\Websocket\MockConnection::class,
|
||||||
|
\BlaxSoftware\LaravelWebSockets\Websocket\MockConnectionSocketPair::class,
|
||||||
|
\BlaxSoftware\LaravelWebSockets\Ipc\SocketPairIpc::class,
|
||||||
|
\BlaxSoftware\LaravelWebSockets\Cache\IpcCache::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($commonClasses as $class) {
|
||||||
|
if (class_exists($class, true)) {
|
||||||
|
new \ReflectionClass($class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log comprehensive server startup information
|
* Log comprehensive server startup information
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,11 @@ class Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$contr = (strpos($event[0], '-') >= 0)
|
$eventPrefix = $event[0];
|
||||||
? implode('', array_map(fn($item) => ucfirst($item), explode('-', $event[0])))
|
|
||||||
: ucfirst($event[0]);
|
|
||||||
|
|
||||||
$vendorcontroller = '\\BlaxSoftware\\LaravelWebSockets\\Websocket\\Controllers\\' . $contr . 'Controller';
|
|
||||||
$appcontroller = '\\App\\Websocket\\Controllers\\' . $contr . 'Controller';
|
|
||||||
$method = static::without_uniquifyer($event[1]);
|
$method = static::without_uniquifyer($event[1]);
|
||||||
|
|
||||||
$controllerClass = class_exists($appcontroller)
|
// Use cached controller resolver for fast lookup
|
||||||
? $appcontroller
|
$controllerClass = ControllerResolver::resolve($eventPrefix);
|
||||||
: (class_exists($vendorcontroller) ? $vendorcontroller : null);
|
|
||||||
|
|
||||||
if (! $controllerClass) {
|
if (! $controllerClass) {
|
||||||
return self::send_error($connection, $message, 'Event could not be associated');
|
return self::send_error($connection, $message, 'Event could not be associated');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,287 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace BlaxSoftware\LaravelWebSockets\Websocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves WebSocket controller classes with caching and fuzzy folder matching.
|
||||||
|
*
|
||||||
|
* Supports:
|
||||||
|
* - Flat controllers: `app.method` → `AppController`
|
||||||
|
* - Kebab-case: `admin-user.method` → `AdminUserController`
|
||||||
|
* - Folder structure: `admin-user.method` → `Admin/UserController` (fuzzy)
|
||||||
|
* - Dynamic discovery: new controllers are found and cached at runtime
|
||||||
|
*/
|
||||||
|
class ControllerResolver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* In-memory cache of event prefix → controller class mappings
|
||||||
|
* This persists for the lifetime of the WebSocket server process
|
||||||
|
*
|
||||||
|
* @var array<string, string|null>
|
||||||
|
*/
|
||||||
|
private static array $controllerCache = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-scanned controller paths for fast lookup
|
||||||
|
* Maps lowercase class name → actual class name with namespace
|
||||||
|
*
|
||||||
|
* @var array<string, string>
|
||||||
|
*/
|
||||||
|
private static array $availableControllers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether controllers have been pre-scanned
|
||||||
|
*/
|
||||||
|
private static bool $scanned = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App controller namespace
|
||||||
|
*/
|
||||||
|
private const APP_NAMESPACE = '\\App\\Websocket\\Controllers\\';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package controller namespace
|
||||||
|
*/
|
||||||
|
private const VENDOR_NAMESPACE = '\\BlaxSoftware\\LaravelWebSockets\\Websocket\\Controllers\\';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve controller class for an event prefix
|
||||||
|
*
|
||||||
|
* @param string $eventPrefix The event prefix (e.g., 'app', 'admin-user', 'admin-user-settings')
|
||||||
|
* @return string|null The fully qualified controller class name, or null if not found
|
||||||
|
*/
|
||||||
|
public static function resolve(string $eventPrefix): ?string
|
||||||
|
{
|
||||||
|
// Check cache first (O(1) lookup)
|
||||||
|
if (array_key_exists($eventPrefix, self::$controllerCache)) {
|
||||||
|
return self::$controllerCache[$eventPrefix];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure controllers are scanned
|
||||||
|
if (!self::$scanned) {
|
||||||
|
self::scanControllers();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the controller
|
||||||
|
$controllerClass = self::findController($eventPrefix);
|
||||||
|
|
||||||
|
// Cache the result (even if null, to avoid repeated lookups)
|
||||||
|
self::$controllerCache[$eventPrefix] = $controllerClass;
|
||||||
|
|
||||||
|
return $controllerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find controller using multiple strategies
|
||||||
|
*/
|
||||||
|
private static function findController(string $eventPrefix): ?string
|
||||||
|
{
|
||||||
|
// Strategy 1: Direct match (e.g., 'app' → 'AppController')
|
||||||
|
$directName = self::kebabToPascal($eventPrefix) . 'Controller';
|
||||||
|
if ($class = self::findInAvailable($directName)) {
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 2: Folder structure (e.g., 'admin-user' → 'Admin/UserController')
|
||||||
|
$parts = explode('-', $eventPrefix);
|
||||||
|
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--) {
|
||||||
|
$folderParts = array_slice($parts, 0, $folderDepth);
|
||||||
|
$nameParts = array_slice($parts, $folderDepth);
|
||||||
|
|
||||||
|
$folder = implode('/', array_map('ucfirst', $folderParts));
|
||||||
|
$name = implode('', array_map('ucfirst', $nameParts)) . 'Controller';
|
||||||
|
|
||||||
|
// Try app namespace with folder
|
||||||
|
$appClass = self::APP_NAMESPACE . str_replace('/', '\\', $folder) . '\\' . $name;
|
||||||
|
if (class_exists($appClass)) {
|
||||||
|
self::$availableControllers[strtolower($name)] = $appClass;
|
||||||
|
return $appClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try vendor namespace with folder
|
||||||
|
$vendorClass = self::VENDOR_NAMESPACE . str_replace('/', '\\', $folder) . '\\' . $name;
|
||||||
|
if (class_exists($vendorClass)) {
|
||||||
|
self::$availableControllers[strtolower($name)] = $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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a controller in the pre-scanned available controllers
|
||||||
|
*/
|
||||||
|
private static function findInAvailable(string $controllerName): ?string
|
||||||
|
{
|
||||||
|
$key = strtolower($controllerName);
|
||||||
|
return self::$availableControllers[$key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert kebab-case to PascalCase
|
||||||
|
* 'admin-user-settings' → 'AdminUserSettings'
|
||||||
|
*/
|
||||||
|
private static function kebabToPascal(string $kebab): string
|
||||||
|
{
|
||||||
|
return implode('', array_map('ucfirst', explode('-', $kebab)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-scan all controller directories and cache the available controllers
|
||||||
|
* This is called once at server startup or on first request
|
||||||
|
*/
|
||||||
|
public static function scanControllers(): void
|
||||||
|
{
|
||||||
|
if (self::$scanned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan app controllers (including subfolders)
|
||||||
|
$appPath = self::getAppControllersPath();
|
||||||
|
if ($appPath && is_dir($appPath)) {
|
||||||
|
self::scanDirectory($appPath, self::APP_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan vendor controllers
|
||||||
|
$vendorPath = __DIR__ . '/Controllers';
|
||||||
|
if (is_dir($vendorPath)) {
|
||||||
|
self::scanDirectory($vendorPath, self::VENDOR_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$scanned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively scan a directory for controller classes
|
||||||
|
*/
|
||||||
|
private static function scanDirectory(string $path, string $namespace, string $subNamespace = ''): void
|
||||||
|
{
|
||||||
|
$iterator = new \DirectoryIterator($path);
|
||||||
|
|
||||||
|
foreach ($iterator as $item) {
|
||||||
|
if ($item->isDot()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($item->isDir()) {
|
||||||
|
// Recurse into subdirectory
|
||||||
|
$folderName = $item->getFilename();
|
||||||
|
$newSubNamespace = $subNamespace . $folderName . '\\';
|
||||||
|
self::scanDirectory($item->getPathname(), $namespace, $newSubNamespace);
|
||||||
|
} elseif ($item->isFile() && $item->getExtension() === 'php') {
|
||||||
|
$fileName = $item->getBasename('.php');
|
||||||
|
|
||||||
|
// Only consider files ending with 'Controller'
|
||||||
|
if (!str_ends_with($fileName, 'Controller')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullClass = $namespace . $subNamespace . $fileName;
|
||||||
|
|
||||||
|
// Verify the class exists (triggers autoload)
|
||||||
|
if (class_exists($fullClass, true)) {
|
||||||
|
// Store with lowercase key for case-insensitive lookup
|
||||||
|
$key = strtolower($fileName);
|
||||||
|
self::$availableControllers[$key] = $fullClass;
|
||||||
|
|
||||||
|
// Also store with folder prefix for folder-based lookup
|
||||||
|
if ($subNamespace) {
|
||||||
|
$folderKey = strtolower(str_replace('\\', '', $subNamespace) . $fileName);
|
||||||
|
self::$availableControllers[$folderKey] = $fullClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the app controllers path
|
||||||
|
*/
|
||||||
|
private static function getAppControllersPath(): ?string
|
||||||
|
{
|
||||||
|
// Try Laravel's app_path if the application is fully booted
|
||||||
|
if (function_exists('app_path')) {
|
||||||
|
try {
|
||||||
|
$path = app_path('Websocket/Controllers');
|
||||||
|
if (is_dir($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
// app_path() might fail if app isn't booted - fall through to fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: try common locations
|
||||||
|
$basePaths = [
|
||||||
|
defined('BASE_PATH') ? BASE_PATH : null,
|
||||||
|
getcwd(),
|
||||||
|
dirname(__DIR__, 5), // Go up from vendor package
|
||||||
|
dirname(__DIR__, 4),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (array_filter($basePaths) as $basePath) {
|
||||||
|
$path = $basePath . '/app/Websocket/Controllers';
|
||||||
|
if (is_dir($path)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the controller cache (useful for testing or hot reload)
|
||||||
|
*/
|
||||||
|
public static function clearCache(): void
|
||||||
|
{
|
||||||
|
self::$controllerCache = [];
|
||||||
|
self::$availableControllers = [];
|
||||||
|
self::$scanned = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache statistics (for debugging)
|
||||||
|
*
|
||||||
|
* @return array{cached: int, available: int, scanned: bool}
|
||||||
|
*/
|
||||||
|
public static function getStats(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'cached' => count(self::$controllerCache),
|
||||||
|
'available' => count(self::$availableControllers),
|
||||||
|
'scanned' => self::$scanned,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force preload of all controllers (call at server startup)
|
||||||
|
*/
|
||||||
|
public static function preload(): void
|
||||||
|
{
|
||||||
|
self::scanControllers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -385,8 +385,9 @@ class Handler implements MessageComponentInterface
|
||||||
$ipc->setupChild();
|
$ipc->setupChild();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Lazy DB reconnect: disconnect now, reconnect only when first query runs
|
||||||
|
// This saves ~5-15ms for methods that don't use the database
|
||||||
DB::disconnect();
|
DB::disconnect();
|
||||||
DB::reconnect();
|
|
||||||
|
|
||||||
$this->setRequest($message, $connection);
|
$this->setRequest($message, $connection);
|
||||||
|
|
||||||
|
|
@ -516,8 +517,9 @@ class Handler implements MessageComponentInterface
|
||||||
string $requestId
|
string $requestId
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
|
// Lazy DB reconnect: disconnect now, reconnect only when first query runs
|
||||||
|
// This saves ~5-15ms for methods that don't use the database
|
||||||
DB::disconnect();
|
DB::disconnect();
|
||||||
DB::reconnect();
|
|
||||||
|
|
||||||
$this->setRequest($message, $connection);
|
$this->setRequest($message, $connection);
|
||||||
$mock = new MockConnection($connection, $requestId);
|
$mock = new MockConnection($connection, $requestId);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BlaxSoftware\LaravelWebSockets\Tests\Unit;
|
||||||
|
|
||||||
|
use BlaxSoftware\LaravelWebSockets\Websocket\ControllerResolver;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class ControllerResolverTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
ControllerResolver::clearCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_resolves_simple_controller_names()
|
||||||
|
{
|
||||||
|
// The package has a PusherController in vendor namespace
|
||||||
|
$result = ControllerResolver::resolve('pusher');
|
||||||
|
|
||||||
|
$this->assertNotNull($result);
|
||||||
|
$this->assertStringContainsString('PusherController', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_caches_resolved_controllers()
|
||||||
|
{
|
||||||
|
// First call scans and caches
|
||||||
|
ControllerResolver::resolve('pusher');
|
||||||
|
|
||||||
|
$stats = ControllerResolver::getStats();
|
||||||
|
$this->assertTrue($stats['scanned']);
|
||||||
|
$this->assertGreaterThan(0, $stats['cached']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_caches_null_for_nonexistent_controllers()
|
||||||
|
{
|
||||||
|
// First call - not found
|
||||||
|
$result1 = ControllerResolver::resolve('nonexistent-controller-xyz');
|
||||||
|
$this->assertNull($result1);
|
||||||
|
|
||||||
|
// Second call - should still be null (cached)
|
||||||
|
$result2 = ControllerResolver::resolve('nonexistent-controller-xyz');
|
||||||
|
$this->assertNull($result2);
|
||||||
|
|
||||||
|
// Check it's in cache
|
||||||
|
$stats = ControllerResolver::getStats();
|
||||||
|
$this->assertGreaterThan(0, $stats['cached']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_converts_kebab_case_to_pascal_case()
|
||||||
|
{
|
||||||
|
// admin-user should try AdminUserController
|
||||||
|
// This tests the internal conversion (we can't directly test private method,
|
||||||
|
// but we can test the resolve behavior)
|
||||||
|
$stats = ControllerResolver::getStats();
|
||||||
|
$this->assertIsArray($stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_clears_cache_correctly()
|
||||||
|
{
|
||||||
|
// Populate cache
|
||||||
|
ControllerResolver::resolve('pusher');
|
||||||
|
|
||||||
|
$statsBefore = ControllerResolver::getStats();
|
||||||
|
$this->assertTrue($statsBefore['scanned']);
|
||||||
|
|
||||||
|
// Clear cache
|
||||||
|
ControllerResolver::clearCache();
|
||||||
|
|
||||||
|
$statsAfter = ControllerResolver::getStats();
|
||||||
|
$this->assertFalse($statsAfter['scanned']);
|
||||||
|
$this->assertEquals(0, $statsAfter['cached']);
|
||||||
|
$this->assertEquals(0, $statsAfter['available']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_preloads_controllers()
|
||||||
|
{
|
||||||
|
ControllerResolver::preload();
|
||||||
|
|
||||||
|
$stats = ControllerResolver::getStats();
|
||||||
|
$this->assertTrue($stats['scanned']);
|
||||||
|
$this->assertGreaterThan(0, $stats['available']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_returns_same_result_on_repeated_calls()
|
||||||
|
{
|
||||||
|
$result1 = ControllerResolver::resolve('pusher');
|
||||||
|
$result2 = ControllerResolver::resolve('pusher');
|
||||||
|
|
||||||
|
$this->assertSame($result1, $result2);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in New Issue