From 8eb882def2fa4cd3b11a4b17bcabad5b43d581a2 Mon Sep 17 00:00:00 2001 From: Prasetyo Wicaksono Date: Sun, 4 Feb 2024 01:25:13 +0700 Subject: [PATCH] feat: add options to listen to swoole server events --- src/swoole/README.md | 14 +++-- src/swoole/composer.json | 3 +- src/swoole/src/CallableRunner.php | 8 ++- src/swoole/src/LaravelRunner.php | 11 +++- src/swoole/src/SwooleEventsTrait.php | 31 ++++++++++ .../SwooleServerEventListenerInterface.php | 18 ++++++ src/swoole/src/SymfonyRunner.php | 11 +++- src/swoole/tests/E2E/runtime.php | 58 +++++++++++++++++++ src/swoole/tests/Unit/CallableRunnerTest.php | 5 ++ src/swoole/tests/Unit/LaravelRunnerTest.php | 8 +++ src/swoole/tests/Unit/SymfonyRunnerTest.php | 11 +++- 11 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 src/swoole/src/SwooleEventsTrait.php create mode 100644 src/swoole/src/SwooleServerEventListenerInterface.php diff --git a/src/swoole/README.md b/src/swoole/README.md index 97c617e..2a39c9d 100644 --- a/src/swoole/README.md +++ b/src/swoole/README.md @@ -54,17 +54,20 @@ return function (array $context) { You can define some configurations using Symfony's Runtime `APP_RUNTIME_OPTIONS` API. -| Option | Description | Default | -| --- | --- | --- | -| `host` | The host where the server should binds to (precedes `SWOOLE_HOST` environment variable) | `127.0.0.1` | -| `port` | The port where the server should be listing (precedes `SWOOLE_PORT` environment variable) | `8000` | -| `mode` | Swoole's server mode (precedes `SWOOLE_MODE` environment variable) | `SWOOLE_PROCESS` | +| Option | Description | Default | +| --- |-----------------------------------------------------------------------------------------------------------------------------------------------------------| --- | +| `host` | The host where the server should binds to (precedes `SWOOLE_HOST` environment variable) | `127.0.0.1` | +| `port` | The port where the server should be listing (precedes `SWOOLE_PORT` environment variable) | `8000` | +| `mode` | Swoole's server mode (precedes `SWOOLE_MODE` environment variable) | `SWOOLE_PROCESS` | | `settings` | All Swoole's server settings ([swoole.co.uk/docs/modules/swoole-server/configuration](https://www.swoole.co.uk/docs/modules/swoole-server/configuration)) | `[]` | +| `server_event_listener_factory` | Factory function to create swoole server event listener class that implement `Runtime\Swoole\SwooleServerEventListenerInterface` | `null` | ```php // public/index.php use App\Kernel; +use Psr\Container\ContainerInterface; +use Runtime\Swoole\SwooleServerEventListenerInterface; $_SERVER['APP_RUNTIME_OPTIONS'] = [ 'host' => '0.0.0.0', @@ -75,6 +78,7 @@ $_SERVER['APP_RUNTIME_OPTIONS'] = [ \Swoole\Constant::OPTION_ENABLE_STATIC_HANDLER => true, \Swoole\Constant::OPTION_DOCUMENT_ROOT => dirname(__DIR__).'/public' ], + 'server_event_listener_factory' => fn(ContainerInterface $container) => $container->get(SwooleServerEventListenerInterface::class) ]; require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; diff --git a/src/swoole/composer.json b/src/swoole/composer.json index 96d247c..23ab93b 100644 --- a/src/swoole/composer.json +++ b/src/swoole/composer.json @@ -18,7 +18,8 @@ "phpunit/phpunit": "^9.6.15", "swoole/ide-helper": "^4.6", "symfony/http-foundation": "^5.4.32 || ^6.3.9 || ^7.0", - "symfony/http-kernel": "^5.4.33 || ^6.3.10 || ^7.0" + "symfony/http-kernel": "^5.4.33 || ^6.3.10 || ^7.0", + "symfony/dependency-injection": "^5.4.33 || ^6.3.10 ||^7.0" }, "conflict": { "ext-swoole": "<4.6.0" diff --git a/src/swoole/src/CallableRunner.php b/src/swoole/src/CallableRunner.php index 8584d01..f93a883 100644 --- a/src/swoole/src/CallableRunner.php +++ b/src/swoole/src/CallableRunner.php @@ -11,6 +11,8 @@ */ class CallableRunner implements RunnerInterface { + use SwooleEventsTrait; + /** @var ServerFactory */ private $serverFactory; /** @var callable */ @@ -24,7 +26,11 @@ public function __construct(ServerFactory $serverFactory, callable $application) public function run(): int { - $this->serverFactory->createServer($this->application)->start(); + $server = $this->serverFactory->createServer($this->application); + + $this->registerSwooleEvents($server, $this->serverFactory->getOptions()); + + $server->start(); return 0; } diff --git a/src/swoole/src/LaravelRunner.php b/src/swoole/src/LaravelRunner.php index 12a0a3f..5454b91 100644 --- a/src/swoole/src/LaravelRunner.php +++ b/src/swoole/src/LaravelRunner.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request as LaravelRequest; +use Psr\Container\ContainerInterface; use Swoole\Http\Request; use Swoole\Http\Response; use Symfony\Component\Runtime\RunnerInterface; @@ -15,6 +16,8 @@ */ class LaravelRunner implements RunnerInterface { + use SwooleEventsTrait; + /** @var ServerFactory */ private $serverFactory; /** @var Kernel */ @@ -28,7 +31,13 @@ public function __construct(ServerFactory $serverFactory, Kernel $application) public function run(): int { - $this->serverFactory->createServer([$this, 'handle'])->start(); + $server = $this->serverFactory->createServer([$this, 'handle']); + + if (($container = $this->application->getApplication()) instanceof ContainerInterface) { + $this->registerSwooleEvents($server, $this->serverFactory->getOptions(), $container); + } + + $server->start(); return 0; } diff --git a/src/swoole/src/SwooleEventsTrait.php b/src/swoole/src/SwooleEventsTrait.php new file mode 100644 index 0000000..2778f35 --- /dev/null +++ b/src/swoole/src/SwooleEventsTrait.php @@ -0,0 +1,31 @@ +on('start', [$eventListener, 'onStart']); + $server->on('workerStart', [$eventListener, 'onWorkerStart']); + $server->on('workerStop', [$eventListener, 'onWorkerStop']); + $server->on('workerError', [$eventListener, 'onWorkerError']); + $server->on('workerExit', [$eventListener, 'onWorkerExit']); + $server->on('task', [$eventListener, 'onTask']); + $server->on('finish', [$eventListener, 'onFinish']); + $server->on('shutdown', [$eventListener, 'onShutdown']); + } +} diff --git a/src/swoole/src/SwooleServerEventListenerInterface.php b/src/swoole/src/SwooleServerEventListenerInterface.php new file mode 100644 index 0000000..7d1a294 --- /dev/null +++ b/src/swoole/src/SwooleServerEventListenerInterface.php @@ -0,0 +1,18 @@ +serverFactory->createServer([$this, 'handle'])->start(); + $server = $this->serverFactory->createServer([$this, 'handle']); + + if ($this->application instanceof KernelInterface) { + $this->registerSwooleEvents($server, $this->serverFactory->getOptions(), $this->application->getContainer()); + } + + $server->start(); return 0; } diff --git a/src/swoole/tests/E2E/runtime.php b/src/swoole/tests/E2E/runtime.php index 9883453..d7c6ee8 100644 --- a/src/swoole/tests/E2E/runtime.php +++ b/src/swoole/tests/E2E/runtime.php @@ -3,20 +3,78 @@ namespace Runtime\Swoole\Tests\E2E; use Runtime\Swoole\Runtime; +use Runtime\Swoole\SwooleServerEventListenerInterface; use Swoole\Constant; +use Swoole\Coroutine; use Swoole\Http\Request; use Swoole\Http\Response; +use Swoole\Server; require_once __DIR__.'/../../vendor/autoload.php'; +$eventListener = new class implements SwooleServerEventListenerInterface { + + #[\Override] public function onStart(Server $server): void + { + echo "-- onServerStart --" . PHP_EOL; + go(function () use ($server) { + Coroutine::waitSignal(SIGINT); + $server->shutdown(); + $server->stop(); + }); + + go(function () use ($server) { + $server->task(['data' => 'Hello World']); + }); + } + + #[\Override] public function onShutdown(Server $server): void + { + echo "-- onServerShutdown --" . PHP_EOL; + } + + #[\Override] public function onWorkerStart(Server $server, int $workerId): void + { + echo "-- onWorkerStart --" . PHP_EOL; + } + + #[\Override] public function onWorkerStop(Server $server, int $workerId): void + { + echo "-- onWorkerStop --" . PHP_EOL; + } + + #[\Override] public function onWorkerError(Server $server, int $workerId, int $exitCode, int $signal): void + { + echo "-- onWorkerError --" . PHP_EOL; + } + + #[\Override] public function onWorkerExit(Server $server, int $workerId): void + { + echo "-- onWorkerExit --" . PHP_EOL; + } + + #[\Override] public function onTask(Server $server, int $taskId, int $srcWorkerId, mixed $data): void + { + echo "-- onTask --" . PHP_EOL; + echo "task payload: " . json_encode($data) . PHP_EOL; + $server->finish($data); + } + + #[\Override] public function onFinish(Server $server, int $taskId, $data): void + { + echo "-- onTaskFinish --" . PHP_EOL; + } +}; $options = [ 'port' => 8001, 'mode' => SWOOLE_BASE, 'settings' => [ Constant::OPTION_WORKER_NUM => 1, + Constant::OPTION_TASK_WORKER_NUM => 1, Constant::OPTION_ENABLE_STATIC_HANDLER => true, Constant::OPTION_DOCUMENT_ROOT => __DIR__.'/static', ], + 'server_event_lister_factory' => fn() => $eventListener ]; $runtime = new Runtime($options); diff --git a/src/swoole/tests/Unit/CallableRunnerTest.php b/src/swoole/tests/Unit/CallableRunnerTest.php index ea7465e..f5ba895 100644 --- a/src/swoole/tests/Unit/CallableRunnerTest.php +++ b/src/swoole/tests/Unit/CallableRunnerTest.php @@ -7,20 +7,25 @@ use PHPUnit\Framework\TestCase; use Runtime\Swoole\CallableRunner; use Runtime\Swoole\ServerFactory; +use Runtime\Swoole\SwooleServerEventListenerInterface; use Swoole\Http\Server; class CallableRunnerTest extends TestCase { public function testRun(): void { + $eventListener = $this->createMock(SwooleServerEventListenerInterface::class); + $application = static function (): void { }; $server = $this->createMock(Server::class); $server->expects(self::once())->method('start'); + $server->expects(self::exactly(8))->method('on')->with($this->anything(), $this->anything()); $factory = $this->createMock(ServerFactory::class); $factory->expects(self::once())->method('createServer')->with(self::equalTo($application))->willReturn($server); + $factory->expects(self::once())->method('getOptions')->willReturn(['server_event_listener_factory' => fn() => $eventListener]); $runner = new CallableRunner($factory, $application); diff --git a/src/swoole/tests/Unit/LaravelRunnerTest.php b/src/swoole/tests/Unit/LaravelRunnerTest.php index c08abad..c31b2c2 100644 --- a/src/swoole/tests/Unit/LaravelRunnerTest.php +++ b/src/swoole/tests/Unit/LaravelRunnerTest.php @@ -6,8 +6,10 @@ use Illuminate\Contracts\Http\Kernel; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerInterface; use Runtime\Swoole\LaravelRunner; use Runtime\Swoole\ServerFactory; +use Runtime\Swoole\SwooleServerEventListenerInterface; use Swoole\Http\Request; use Swoole\Http\Response; use Swoole\Http\Server; @@ -17,13 +19,19 @@ class LaravelRunnerTest extends TestCase { public function testRun(): void { + $container = $this->createMock(ContainerInterface::class); + $eventListener = $this->createMock(SwooleServerEventListenerInterface::class); + $application = $this->createMock(Kernel::class); + $application->expects(self::once())->method('getApplication')->willReturn($container); $server = $this->createMock(Server::class); $server->expects(self::once())->method('start'); + $server->expects(self::exactly(8))->method('on')->with($this->anything(), $this->anything()); $factory = $this->createMock(ServerFactory::class); $factory->expects(self::once())->method('createServer')->willReturn($server); + $factory->expects(self::once())->method('getOptions')->willReturn(['server_event_listener_factory' => fn() => $eventListener]); $runner = new LaravelRunner($factory, $application); diff --git a/src/swoole/tests/Unit/SymfonyRunnerTest.php b/src/swoole/tests/Unit/SymfonyRunnerTest.php index 8deee0a..62b7ec8 100644 --- a/src/swoole/tests/Unit/SymfonyRunnerTest.php +++ b/src/swoole/tests/Unit/SymfonyRunnerTest.php @@ -6,24 +6,33 @@ use PHPUnit\Framework\TestCase; use Runtime\Swoole\ServerFactory; +use Runtime\Swoole\SwooleServerEventListenerInterface; use Runtime\Swoole\SymfonyRunner; use Swoole\Http\Request; use Swoole\Http\Response; use Swoole\Http\Server; +use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface; use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelInterface; class SymfonyRunnerTest extends TestCase { public function testRun(): void { - $application = $this->createMock(HttpKernelInterface::class); + $container = $this->createMock(SymfonyContainerInterface::class); + $eventListener = $this->createMock(SwooleServerEventListenerInterface::class); + + $application = $this->createMock(KernelInterface::class); + $application->expects(self::once())->method('getContainer')->willReturn($container); $server = $this->createMock(Server::class); $server->expects(self::once())->method('start'); + $server->expects(self::exactly(8))->method('on')->with($this->anything(), $this->anything()); $factory = $this->createMock(ServerFactory::class); $factory->expects(self::once())->method('createServer')->willReturn($server); + $factory->expects(self::once())->method('getOptions')->willReturn(['server_event_listener_factory' => fn() => $eventListener]); $runner = new SymfonyRunner($factory, $application);