[Server] Simplify server entrypoint with run-based transport lifecycle (#103)
* feat: replace connect/listen flow with run lifecycle * feat(transport): add phpstan generics for typed transport returns * refactor: standardize variable naming for MCP server instances in transport handling * test: remove unnecessary phpstan-ignore annotation in DocBlockTestFixture
This commit is contained in:
parent
e2243bc2d9
commit
2dbd411194
19 changed files with 82 additions and 74 deletions
10
README.md
10
README.md
|
|
@ -108,8 +108,8 @@ $server = Server::builder()
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
$transport = new StdioTransport();
|
$transport = new StdioTransport();
|
||||||
$server->connect($transport);
|
|
||||||
$transport->listen();
|
$server->run($transport);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Configure Your MCP Client
|
### 3. Configure Your MCP Client
|
||||||
|
|
@ -175,15 +175,13 @@ $server = Server::builder()
|
||||||
**STDIO Transport** (Command-line integration):
|
**STDIO Transport** (Command-line integration):
|
||||||
```php
|
```php
|
||||||
$transport = new StdioTransport();
|
$transport = new StdioTransport();
|
||||||
$server->connect($transport);
|
$server->run($transport);
|
||||||
$transport->listen();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**HTTP Transport** (Web-based communication):
|
**HTTP Transport** (Web-based communication):
|
||||||
```php
|
```php
|
||||||
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
|
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
|
||||||
$server->connect($transport);
|
$response = $server->run($transport);
|
||||||
$response = $transport->listen();
|
|
||||||
// Handle $response in your web application
|
// Handle $response in your web application
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new SomeTransport();
|
$transport = new SomeTransport();
|
||||||
|
|
||||||
$server->connect($transport);
|
$result = $server->run($transport); // Blocks for STDIO, returns a response for HTTP
|
||||||
|
|
||||||
$transport->listen(); // For STDIO, or handle response for HTTP
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## STDIO Transport
|
## STDIO Transport
|
||||||
|
|
@ -70,9 +68,9 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StdioTransport();
|
$transport = new StdioTransport();
|
||||||
|
|
||||||
$server->connect($transport);
|
$status = $server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
exit($status); // 0 on clean shutdown, non-zero if STDIN errored
|
||||||
```
|
```
|
||||||
|
|
||||||
### Client Configuration
|
### Client Configuration
|
||||||
|
|
@ -138,24 +136,20 @@ use Nyholm\Psr7\Factory\Psr17Factory;
|
||||||
use Nyholm\Psr7Server\ServerRequestCreator;
|
use Nyholm\Psr7Server\ServerRequestCreator;
|
||||||
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||||||
|
|
||||||
// Create PSR-7 request from globals
|
|
||||||
$psr17Factory = new Psr17Factory();
|
$psr17Factory = new Psr17Factory();
|
||||||
$creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
|
$creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
|
||||||
$request = $creator->fromGlobals();
|
$request = $creator->fromGlobals();
|
||||||
|
|
||||||
// Build server
|
|
||||||
$server = Server::builder()
|
$server = Server::builder()
|
||||||
->setServerInfo('HTTP Server', '1.0.0')
|
->setServerInfo('HTTP Server', '1.0.0')
|
||||||
->setDiscovery(__DIR__, ['.'])
|
->setDiscovery(__DIR__, ['.'])
|
||||||
->setSession(new FileSessionStore(__DIR__ . '/sessions')) // HTTP needs persistent sessions
|
->setSession(new FileSessionStore(__DIR__ . '/sessions')) // HTTP needs persistent sessions
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
// Process request and get response
|
|
||||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||||
$server->connect($transport);
|
|
||||||
$response = $transport->listen();
|
|
||||||
|
|
||||||
// Emit response
|
$response = $server->run($transport);
|
||||||
|
|
||||||
(new SapiEmitter())->emit($response);
|
(new SapiEmitter())->emit($response);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -187,7 +181,7 @@ use Mcp\Server\Transport\StreamableHttpTransport;
|
||||||
class McpController
|
class McpController
|
||||||
{
|
{
|
||||||
#[Route('/mcp', name: 'mcp_endpoint']
|
#[Route('/mcp', name: 'mcp_endpoint']
|
||||||
public function handle(Request $request, Server $mcpServer): Response
|
public function handle(Request $request, Server $server): Response
|
||||||
{
|
{
|
||||||
// Create PSR-7 factories
|
// Create PSR-7 factories
|
||||||
$psr17Factory = new Psr17Factory();
|
$psr17Factory = new Psr17Factory();
|
||||||
|
|
@ -199,8 +193,7 @@ class McpController
|
||||||
|
|
||||||
// Process with MCP
|
// Process with MCP
|
||||||
$transport = new StreamableHttpTransport($psrRequest, $psr17Factory, $psr17Factory);
|
$transport = new StreamableHttpTransport($psrRequest, $psr17Factory, $psr17Factory);
|
||||||
$mcpServer->connect($transport);
|
$psrResponse = $server->run($transport);
|
||||||
$psrResponse = $transport->listen();
|
|
||||||
|
|
||||||
// Convert PSR-7 response back to Symfony
|
// Convert PSR-7 response back to Symfony
|
||||||
return $httpFoundationFactory->createResponse($psrResponse);
|
return $httpFoundationFactory->createResponse($psrResponse);
|
||||||
|
|
@ -230,17 +223,16 @@ use Nyholm\Psr7\Factory\Psr17Factory;
|
||||||
|
|
||||||
class McpController
|
class McpController
|
||||||
{
|
{
|
||||||
public function handle(ServerRequestInterface $request, Server $mcpServer): ResponseInterface
|
public function handle(ServerRequestInterface $request, Server $server): ResponseInterface
|
||||||
{
|
{
|
||||||
$psr17Factory = new Psr17Factory();
|
$psr17Factory = new Psr17Factory();
|
||||||
|
|
||||||
// Create and connect the MCP HTTP transport
|
// Create the MCP HTTP transport
|
||||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||||
$mcpServer->connect($transport);
|
|
||||||
|
|
||||||
// Process MCP request and return PSR-7 response
|
// Process MCP request and return PSR-7 response
|
||||||
// Laravel automatically handles PSR-7 responses
|
// Laravel automatically handles PSR-7 responses
|
||||||
return $transport->listen();
|
return $server->run($transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,7 +247,6 @@ Slim Framework works natively with PSR-7.
|
||||||
Create a route handler using Slim's built-in factories and container:
|
Create a route handler using Slim's built-in factories and container:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
use Psr\Container\ContainerInterface;
|
|
||||||
use Slim\Factory\AppFactory;
|
use Slim\Factory\AppFactory;
|
||||||
use Slim\Psr7\Factory\ResponseFactory;
|
use Slim\Psr7\Factory\ResponseFactory;
|
||||||
use Slim\Psr7\Factory\StreamFactory;
|
use Slim\Psr7\Factory\StreamFactory;
|
||||||
|
|
@ -263,25 +254,19 @@ use Mcp\Server;
|
||||||
use Mcp\Server\Transport\StreamableHttpTransport;
|
use Mcp\Server\Transport\StreamableHttpTransport;
|
||||||
|
|
||||||
$app = AppFactory::create();
|
$app = AppFactory::create();
|
||||||
$container = $app->getContainer();
|
|
||||||
|
|
||||||
$container->set('mcpServer', function (ContainerInterface $container) {
|
$app->any('/mcp', function ($request, $response) {
|
||||||
return Server::builder()
|
$server = Server::builder()
|
||||||
->setServerInfo('My MCP Server', '1.0.0')
|
->setServerInfo('My MCP Server', '1.0.0')
|
||||||
->setDiscovery(__DIR__, ['.'])
|
->setDiscovery(__DIR__, ['.'])
|
||||||
->build();
|
->build();
|
||||||
});
|
|
||||||
|
|
||||||
$app->any('/mcp', function ($request, $response) {
|
|
||||||
$mcpServer = $this->get('mcpServer');
|
|
||||||
|
|
||||||
$responseFactory = new ResponseFactory();
|
$responseFactory = new ResponseFactory();
|
||||||
$streamFactory = new StreamFactory();
|
$streamFactory = new StreamFactory();
|
||||||
|
|
||||||
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
|
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
|
||||||
$mcpServer->connect($transport);
|
|
||||||
|
|
||||||
return $transport->listen();
|
return $server->run($transport);
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,8 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StdioTransport(logger: logger());
|
$transport = new StdioTransport(logger: logger());
|
||||||
|
|
||||||
$server->connect($transport);
|
$result = $server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
|
||||||
|
|
||||||
logger()->info('Server listener stopped gracefully.');
|
exit($result);
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,6 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||||
|
|
||||||
$server->connect($transport);
|
$response = $server->run($transport);
|
||||||
|
|
||||||
$response = $transport->listen();
|
|
||||||
|
|
||||||
(new SapiEmitter())->emit($response);
|
(new SapiEmitter())->emit($response);
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,6 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||||
|
|
||||||
$server->connect($transport);
|
$response = $server->run($transport);
|
||||||
|
|
||||||
$response = $transport->listen();
|
|
||||||
|
|
||||||
(new SapiEmitter())->emit($response);
|
(new SapiEmitter())->emit($response);
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,6 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||||
|
|
||||||
$server->connect($transport);
|
$response = $server->run($transport);
|
||||||
|
|
||||||
$response = $transport->listen();
|
|
||||||
|
|
||||||
(new SapiEmitter())->emit($response);
|
(new SapiEmitter())->emit($response);
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,6 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||||
|
|
||||||
$server->connect($transport);
|
$response = $server->run($transport);
|
||||||
|
|
||||||
$response = $transport->listen();
|
|
||||||
|
|
||||||
(new SapiEmitter())->emit($response);
|
(new SapiEmitter())->emit($response);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StdioTransport(logger: logger());
|
$transport = new StdioTransport(logger: logger());
|
||||||
|
|
||||||
$server->connect($transport);
|
$result = $server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
|
||||||
|
|
||||||
logger()->info('Server listener stopped gracefully.');
|
exit($result);
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StdioTransport(logger: logger());
|
$transport = new StdioTransport(logger: logger());
|
||||||
|
|
||||||
$server->connect($transport);
|
$result = $server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
|
||||||
|
|
||||||
logger()->info('Server listener stopped gracefully.');
|
exit($result);
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StdioTransport(logger: logger());
|
$transport = new StdioTransport(logger: logger());
|
||||||
|
|
||||||
$server->connect($transport);
|
$result = $server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
|
||||||
|
|
||||||
logger()->info('Server listener stopped gracefully.');
|
exit($result);
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StdioTransport(logger: logger());
|
$transport = new StdioTransport(logger: logger());
|
||||||
|
|
||||||
$server->connect($transport);
|
$result = $server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
|
||||||
|
|
||||||
logger()->info('Server listener stopped gracefully.');
|
exit($result);
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ $server = Server::builder()
|
||||||
|
|
||||||
$transport = new StdioTransport(logger: logger());
|
$transport = new StdioTransport(logger: logger());
|
||||||
|
|
||||||
$server->connect($transport);
|
$result = $server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
|
||||||
|
|
||||||
logger()->info('Server listener stopped gracefully.');
|
exit($result);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,14 @@ final class Server
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connect(TransportInterface $transport): void
|
/**
|
||||||
|
* @template TResult
|
||||||
|
*
|
||||||
|
* @param TransportInterface<TResult> $transport
|
||||||
|
*
|
||||||
|
* @return TResult
|
||||||
|
*/
|
||||||
|
public function run(TransportInterface $transport): mixed
|
||||||
{
|
{
|
||||||
$transport->initialize();
|
$transport->initialize();
|
||||||
|
|
||||||
|
|
@ -56,5 +63,11 @@ final class Server
|
||||||
$transport->onSessionEnd(function (Uuid $sessionId) {
|
$transport->onSessionEnd(function (Uuid $sessionId) {
|
||||||
$this->jsonRpcHandler->destroySession($sessionId);
|
$this->jsonRpcHandler->destroySession($sessionId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $transport->listen();
|
||||||
|
} finally {
|
||||||
|
$transport->close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ namespace Mcp\Server\Transport;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @implements TransportInterface<null>
|
||||||
|
*
|
||||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||||
*/
|
*/
|
||||||
class InMemoryTransport implements TransportInterface
|
class InMemoryTransport implements TransportInterface
|
||||||
|
|
@ -50,6 +52,9 @@ class InMemoryTransport implements TransportInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
public function listen(): mixed
|
public function listen(): mixed
|
||||||
{
|
{
|
||||||
foreach ($this->messages as $message) {
|
foreach ($this->messages as $message) {
|
||||||
|
|
@ -60,6 +65,7 @@ class InMemoryTransport implements TransportInterface
|
||||||
|
|
||||||
if (\is_callable($this->sessionDestroyListener) && null !== $this->sessionId) {
|
if (\is_callable($this->sessionDestroyListener) && null !== $this->sessionId) {
|
||||||
\call_user_func($this->sessionDestroyListener, $this->sessionId);
|
\call_user_func($this->sessionDestroyListener, $this->sessionId);
|
||||||
|
$this->sessionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -74,6 +80,7 @@ class InMemoryTransport implements TransportInterface
|
||||||
{
|
{
|
||||||
if (\is_callable($this->sessionDestroyListener) && null !== $this->sessionId) {
|
if (\is_callable($this->sessionDestroyListener) && null !== $this->sessionId) {
|
||||||
\call_user_func($this->sessionDestroyListener, $this->sessionId);
|
\call_user_func($this->sessionDestroyListener, $this->sessionId);
|
||||||
|
$this->sessionId = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @implements TransportInterface<int>
|
||||||
|
*
|
||||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||||
*/
|
*/
|
||||||
class StdioTransport implements TransportInterface
|
class StdioTransport implements TransportInterface
|
||||||
|
|
@ -59,13 +61,18 @@ class StdioTransport implements TransportInterface
|
||||||
fwrite($this->output, $data.\PHP_EOL);
|
fwrite($this->output, $data.\PHP_EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listen(): mixed
|
public function listen(): int
|
||||||
{
|
{
|
||||||
$this->logger->info('StdioTransport is listening for messages on STDIN...');
|
$this->logger->info('StdioTransport is listening for messages on STDIN...');
|
||||||
|
|
||||||
|
$status = 0;
|
||||||
while (!feof($this->input)) {
|
while (!feof($this->input)) {
|
||||||
$line = fgets($this->input);
|
$line = fgets($this->input);
|
||||||
if (false === $line) {
|
if (false === $line) {
|
||||||
|
if (!feof($this->input)) {
|
||||||
|
$status = 1;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,9 +89,10 @@ class StdioTransport implements TransportInterface
|
||||||
|
|
||||||
if (\is_callable($this->sessionEndListener) && null !== $this->sessionId) {
|
if (\is_callable($this->sessionEndListener) && null !== $this->sessionId) {
|
||||||
\call_user_func($this->sessionEndListener, $this->sessionId);
|
\call_user_func($this->sessionEndListener, $this->sessionId);
|
||||||
|
$this->sessionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onSessionEnd(callable $listener): void
|
public function onSessionEnd(callable $listener): void
|
||||||
|
|
@ -96,6 +104,7 @@ class StdioTransport implements TransportInterface
|
||||||
{
|
{
|
||||||
if (\is_callable($this->sessionEndListener) && null !== $this->sessionId) {
|
if (\is_callable($this->sessionEndListener) && null !== $this->sessionId) {
|
||||||
\call_user_func($this->sessionEndListener, $this->sessionId);
|
\call_user_func($this->sessionEndListener, $this->sessionId);
|
||||||
|
$this->sessionId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_resource($this->input)) {
|
if (\is_resource($this->input)) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ use Psr\Log\NullLogger;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @implements TransportInterface<ResponseInterface>
|
||||||
|
*
|
||||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||||
*/
|
*/
|
||||||
class StreamableHttpTransport implements TransportInterface
|
class StreamableHttpTransport implements TransportInterface
|
||||||
|
|
@ -78,7 +80,7 @@ class StreamableHttpTransport implements TransportInterface
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listen(): mixed
|
public function listen(): ResponseInterface
|
||||||
{
|
{
|
||||||
return match ($this->request->getMethod()) {
|
return match ($this->request->getMethod()) {
|
||||||
'OPTIONS' => $this->handleOptionsRequest(),
|
'OPTIONS' => $this->handleOptionsRequest(),
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ namespace Mcp\Server\Transport;
|
||||||
use Symfony\Component\Uid\Uuid;
|
use Symfony\Component\Uid\Uuid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @template TResult
|
||||||
|
*
|
||||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
|
@ -38,7 +40,7 @@ interface TransportInterface
|
||||||
* - For a single-request transport like HTTP, this will process the request
|
* - For a single-request transport like HTTP, this will process the request
|
||||||
* and return a result (e.g., a PSR-7 Response) to be sent to the client.
|
* and return a result (e.g., a PSR-7 Response) to be sent to the client.
|
||||||
*
|
*
|
||||||
* @return mixed the result of the transport's execution, if any
|
* @return TResult the result of the transport's execution, if any
|
||||||
*/
|
*/
|
||||||
public function listen(): mixed;
|
public function listen(): mixed;
|
||||||
|
|
||||||
|
|
@ -63,6 +65,7 @@ interface TransportInterface
|
||||||
*
|
*
|
||||||
* This method should be called when the transport is no longer needed.
|
* This method should be called when the transport is no longer needed.
|
||||||
* It should clean up any resources and close any connections.
|
* It should clean up any resources and close any connections.
|
||||||
|
* `Server::run()` calls this automatically after `listen()` exits.
|
||||||
*/
|
*/
|
||||||
public function close(): void;
|
public function close(): void;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ class DocBlockTestFixture
|
||||||
* @deprecated use newMethod() instead
|
* @deprecated use newMethod() instead
|
||||||
* @see DocBlockTestFixture::newMethod()
|
* @see DocBlockTestFixture::newMethod()
|
||||||
*/
|
*/
|
||||||
public function methodWithMultipleTags(float $value): bool /* @phpstan-ignore throws.unusedType */
|
public function methodWithMultipleTags(float $value): bool
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,16 +32,15 @@ class ServerTest extends TestCase
|
||||||
|
|
||||||
$transport = $this->getMockBuilder(InMemoryTransport::class)
|
$transport = $this->getMockBuilder(InMemoryTransport::class)
|
||||||
->setConstructorArgs([['foo', 'bar']])
|
->setConstructorArgs([['foo', 'bar']])
|
||||||
->onlyMethods(['send'])
|
->onlyMethods(['send', 'close'])
|
||||||
->getMock();
|
->getMock();
|
||||||
$transport->expects($this->exactly(2))->method('send')->willReturnOnConsecutiveCalls(
|
$transport->expects($this->exactly(2))->method('send')->willReturnOnConsecutiveCalls(
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
$transport->expects($this->once())->method('close');
|
||||||
|
|
||||||
$server = new Server($handler);
|
$server = new Server($handler);
|
||||||
$server->connect($transport);
|
$server->run($transport);
|
||||||
|
|
||||||
$transport->listen();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue