Skip to content

Commit 662eb31

Browse files
refactor: simplify session and cache configuration API
1 parent ba62387 commit 662eb31

File tree

4 files changed

+145
-63
lines changed

4 files changed

+145
-63
lines changed

src/Configuration.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ class Configuration
2525
* @param LoopInterface $loop ReactPHP Event Loop instance.
2626
* @param CacheInterface|null $cache Optional PSR-16 Cache instance for registry/state.
2727
* @param ContainerInterface $container PSR-11 DI Container for resolving handlers/dependencies.
28-
* @param int $definitionCacheTtl TTL in seconds for cached definitions (if cache is provided).
2928
* @param int $paginationLimit Maximum number of items to return for list methods.
3029
*/
3130
public function __construct(
@@ -35,8 +34,6 @@ public function __construct(
3534
public readonly LoopInterface $loop,
3635
public readonly ?CacheInterface $cache,
3736
public readonly ContainerInterface $container,
38-
public readonly int $definitionCacheTtl = 3600,
3937
public readonly int $paginationLimit = 50,
40-
) {
41-
}
38+
) {}
4239
}

src/ServerBuilder.php

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ final class ServerBuilder
4646

4747
private ?SessionHandlerInterface $sessionHandler = null;
4848

49-
private ?int $sessionTtl = 3600;
49+
private ?string $sessionDriver = null;
5050

51-
private ?int $definitionCacheTtl = 3600;
51+
private ?int $sessionTtl = 3600;
5252

5353
private ?int $paginationLimit = 50;
5454

@@ -88,9 +88,7 @@ final class ServerBuilder
8888
* > */
8989
private array $manualPrompts = [];
9090

91-
public function __construct()
92-
{
93-
}
91+
public function __construct() {}
9492

9593
/**
9694
* Sets the server's identity. Required.
@@ -133,36 +131,42 @@ public function withLogger(LoggerInterface $logger): self
133131
}
134132

135133
/**
136-
* Provides a PSR-16 cache instance and optionally sets the TTL for definition caching.
137-
* If no cache is provided, definition caching is disabled (uses default FileCache if possible).
134+
* Provides a PSR-16 cache instance used for all internal caching.
138135
*/
139-
public function withCache(CacheInterface $cache, int $definitionCacheTtl = 3600): self
136+
public function withCache(CacheInterface $cache): self
140137
{
141138
$this->cache = $cache;
142-
$this->definitionCacheTtl = $definitionCacheTtl > 0 ? $definitionCacheTtl : 3600;
143139

144140
return $this;
145141
}
146142

147-
public function withSessionHandler(SessionHandlerInterface $sessionHandler, int $sessionTtl = 3600): self
143+
/**
144+
* Configures session handling with a specific driver.
145+
*
146+
* @param 'array' | 'cache' $driver The session driver: 'array' for in-memory sessions, 'cache' for cache-backed sessions
147+
* @param int $ttl Session time-to-live in seconds. Defaults to 3600.
148+
*/
149+
public function withSession(string $driver, int $ttl = 3600): self
148150
{
149-
$this->sessionHandler = $sessionHandler;
150-
$this->sessionTtl = $sessionTtl;
151+
if (!in_array($driver, ['array', 'cache'], true)) {
152+
throw new \InvalidArgumentException(
153+
"Unsupported session driver '{$driver}'. Only 'array' and 'cache' drivers are supported. " .
154+
"For custom session handling, use withSessionHandler() instead."
155+
);
156+
}
151157

152-
return $this;
153-
}
154-
155-
public function withArraySessionHandler(int $sessionTtl = 3600): self
156-
{
157-
$this->sessionHandler = new ArraySessionHandler($sessionTtl);
158-
$this->sessionTtl = $sessionTtl;
158+
$this->sessionDriver = $driver;
159+
$this->sessionTtl = $ttl;
159160

160161
return $this;
161162
}
162163

163-
public function withCacheSessionHandler(CacheInterface $cache, int $sessionTtl = 3600): self
164+
/**
165+
* Provides a custom session handler.
166+
*/
167+
public function withSessionHandler(SessionHandlerInterface $sessionHandler, int $sessionTtl = 3600): self
164168
{
165-
$this->sessionHandler = new CacheSessionHandler($cache, $sessionTtl);
169+
$this->sessionHandler = $sessionHandler;
166170
$this->sessionTtl = $sessionTtl;
167171

168172
return $this;
@@ -253,11 +257,10 @@ public function build(): Server
253257
loop: $loop,
254258
cache: $cache,
255259
container: $container,
256-
definitionCacheTtl: $this->definitionCacheTtl ?? 3600,
257260
paginationLimit: $this->paginationLimit ?? 50
258261
);
259262

260-
$sessionHandler = $this->sessionHandler ?? new ArraySessionHandler(3600);
263+
$sessionHandler = $this->createSessionHandler();
261264
$sessionManager = new SessionManager($sessionHandler, $logger, $loop, $this->sessionTtl);
262265
$registry = new Registry($logger, $cache, $sessionManager);
263266
$protocol = new Protocol($configuration, $registry, $sessionManager);
@@ -410,6 +413,47 @@ private function registerManualElements(Registry $registry, LoggerInterface $log
410413
$logger->debug('Manual element registration complete.');
411414
}
412415

416+
/**
417+
* Creates the appropriate session handler based on configuration.
418+
*
419+
* @throws ConfigurationException If cache driver is selected but no cache is provided
420+
*/
421+
private function createSessionHandler(): SessionHandlerInterface
422+
{
423+
// If a custom session handler was provided, use it
424+
if ($this->sessionHandler !== null) {
425+
return $this->sessionHandler;
426+
}
427+
428+
// If no session driver was specified, default to array
429+
if ($this->sessionDriver === null) {
430+
return new ArraySessionHandler($this->sessionTtl ?? 3600);
431+
}
432+
433+
// Create handler based on driver
434+
return match ($this->sessionDriver) {
435+
'array' => new ArraySessionHandler($this->sessionTtl ?? 3600),
436+
'cache' => $this->createCacheSessionHandler(),
437+
default => throw new ConfigurationException("Unsupported session driver: {$this->sessionDriver}")
438+
};
439+
}
440+
441+
/**
442+
* Creates a cache-based session handler.
443+
*
444+
* @throws ConfigurationException If no cache is configured
445+
*/
446+
private function createCacheSessionHandler(): CacheSessionHandler
447+
{
448+
if ($this->cache === null) {
449+
throw new ConfigurationException(
450+
"Cache session driver requires a cache instance. Please configure a cache using withCache() before using withSession('cache')."
451+
);
452+
}
453+
454+
return new CacheSessionHandler($this->cache, $this->sessionTtl ?? 3600);
455+
}
456+
413457
private function getCompletionProviders(\ReflectionMethod $reflectionMethod): array
414458
{
415459
$completionProviders = [];

tests/Unit/ConfigurationTest.php

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
});
2626

2727
it('constructs configuration object with all properties', function () {
28-
$ttl = 1800;
2928
$paginationLimit = 100;
3029
$config = new Configuration(
3130
serverInfo: $this->serverInfo,
@@ -34,7 +33,6 @@
3433
loop: $this->loop,
3534
cache: $this->cache,
3635
container: $this->container,
37-
definitionCacheTtl: $ttl,
3836
paginationLimit: $paginationLimit
3937
);
4038

@@ -44,23 +42,9 @@
4442
expect($config->loop)->toBe($this->loop);
4543
expect($config->cache)->toBe($this->cache);
4644
expect($config->container)->toBe($this->container);
47-
expect($config->definitionCacheTtl)->toBe($ttl);
4845
expect($config->paginationLimit)->toBe($paginationLimit);
4946
});
5047

51-
it('constructs configuration object with default TTL', function () {
52-
$config = new Configuration(
53-
serverInfo: $this->serverInfo,
54-
capabilities: $this->capabilities,
55-
logger: $this->logger,
56-
loop: $this->loop,
57-
cache: $this->cache,
58-
container: $this->container
59-
);
60-
61-
expect($config->definitionCacheTtl)->toBe(3600); // Default value
62-
});
63-
6448
it('constructs configuration object with default pagination limit', function () {
6549
$config = new Configuration(
6650
serverInfo: $this->serverInfo,

tests/Unit/ServerBuilderTest.php

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,10 @@ public function getCompletions(string $currentValue, SessionInterface $session):
9696
expect(getPrivateProperty($this->builder, 'logger'))->toBe($logger);
9797
});
9898

99-
it('sets cache and TTL correctly', function () {
100-
$cache = Mockery::mock(CacheInterface::class);
101-
$this->builder->withCache($cache, 1800);
102-
expect(getPrivateProperty($this->builder, 'cache'))->toBe($cache);
103-
expect(getPrivateProperty($this->builder, 'definitionCacheTtl'))->toBe(1800);
104-
});
105-
106-
it('sets cache with default TTL if TTL not provided', function () {
99+
it('sets cache correctly', function () {
107100
$cache = Mockery::mock(CacheInterface::class);
108101
$this->builder->withCache($cache);
109-
expect(getPrivateProperty($this->builder, 'definitionCacheTtl'))->toBe(3600);
102+
expect(getPrivateProperty($this->builder, 'cache'))->toBe($cache);
110103
});
111104

112105
it('sets session handler correctly', function () {
@@ -116,22 +109,86 @@ public function getCompletions(string $currentValue, SessionInterface $session):
116109
expect(getPrivateProperty($this->builder, 'sessionTtl'))->toBe(7200);
117110
});
118111

119-
it('sets ArraySessionHandler correctly', function () {
120-
$this->builder->withArraySessionHandler(1800);
121-
expect(getPrivateProperty($this->builder, 'sessionHandler'))->toBeInstanceOf(ArraySessionHandler::class);
122-
expect(getPrivateProperty($this->builder, 'sessionHandler')->ttl)->toBe(1800);
112+
it('sets session driver to array correctly', function () {
113+
$this->builder->withSession('array', 1800);
114+
expect(getPrivateProperty($this->builder, 'sessionDriver'))->toBe('array');
123115
expect(getPrivateProperty($this->builder, 'sessionTtl'))->toBe(1800);
124116
});
125117

126-
it('sets CacheSessionHandler correctly', function () {
118+
it('sets session driver to cache correctly', function () {
119+
$this->builder->withSession('cache', 900);
120+
expect(getPrivateProperty($this->builder, 'sessionDriver'))->toBe('cache');
121+
expect(getPrivateProperty($this->builder, 'sessionTtl'))->toBe(900);
122+
});
123+
124+
it('uses default TTL when not specified for session', function () {
125+
$this->builder->withSession('array');
126+
expect(getPrivateProperty($this->builder, 'sessionTtl'))->toBe(3600);
127+
});
128+
129+
it('throws exception for invalid session driver', function () {
130+
$this->builder->withSession('redis');
131+
})->throws(\InvalidArgumentException::class, "Unsupported session driver 'redis'. Only 'array' and 'cache' drivers are supported.");
132+
133+
it('throws exception for cache session driver without cache during build', function () {
134+
$this->builder
135+
->withServerInfo('Test', '1.0')
136+
->withSession('cache')
137+
->build();
138+
})->throws(ConfigurationException::class, 'Cache session driver requires a cache instance');
139+
140+
it('creates ArraySessionHandler when array driver is specified', function () {
141+
$server = $this->builder
142+
->withServerInfo('Test', '1.0')
143+
->withSession('array', 1800)
144+
->build();
145+
146+
$sessionManager = $server->getSessionManager();
147+
$smReflection = new ReflectionClass(SessionManager::class);
148+
$handlerProp = $smReflection->getProperty('handler');
149+
$handlerProp->setAccessible(true);
150+
$handler = $handlerProp->getValue($sessionManager);
151+
152+
expect($handler)->toBeInstanceOf(ArraySessionHandler::class);
153+
expect($handler->ttl)->toBe(1800);
154+
});
155+
156+
it('creates CacheSessionHandler when cache driver is specified', function () {
127157
$cache = Mockery::mock(CacheInterface::class);
128158
$cache->shouldReceive('get')->with('mcp_session_index', [])->andReturn([]);
129-
$this->builder->withCacheSessionHandler($cache, 900);
130-
$sessionHandler = getPrivateProperty($this->builder, 'sessionHandler');
131-
expect($sessionHandler)->toBeInstanceOf(CacheSessionHandler::class);
132-
expect($sessionHandler->cache)->toBe($cache);
133-
expect($sessionHandler->ttl)->toBe(900);
134-
expect(getPrivateProperty($this->builder, 'sessionTtl'))->toBe(900);
159+
160+
$server = $this->builder
161+
->withServerInfo('Test', '1.0')
162+
->withCache($cache)
163+
->withSession('cache', 900)
164+
->build();
165+
166+
$sessionManager = $server->getSessionManager();
167+
$smReflection = new ReflectionClass(SessionManager::class);
168+
$handlerProp = $smReflection->getProperty('handler');
169+
$handlerProp->setAccessible(true);
170+
$handler = $handlerProp->getValue($sessionManager);
171+
172+
expect($handler)->toBeInstanceOf(CacheSessionHandler::class);
173+
expect($handler->cache)->toBe($cache);
174+
expect($handler->ttl)->toBe(900);
175+
});
176+
177+
it('prefers custom session handler over session driver', function () {
178+
$customHandler = Mockery::mock(SessionHandlerInterface::class);
179+
180+
$server = $this->builder
181+
->withServerInfo('Test', '1.0')
182+
->withSession('array')
183+
->withSessionHandler($customHandler, 1200)
184+
->build();
185+
186+
$sessionManager = $server->getSessionManager();
187+
$smReflection = new ReflectionClass(SessionManager::class);
188+
$handlerProp = $smReflection->getProperty('handler');
189+
$handlerProp->setAccessible(true);
190+
191+
expect($handlerProp->getValue($sessionManager))->toBe($customHandler);
135192
});
136193

137194

0 commit comments

Comments
 (0)