phpdot / container-swoole
Swoole context provider and per-request dispatcher for phpdot/container
Requires
- php: >=8.4
- ext-swoole: >=6.2
- phpdot/container: ^1.9
- phpdot/contracts: ^1.5
- psr/container: ^1.1 || ^2.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.0
- swoole/ide-helper: ^6.0
README
Swoole adapter for phpdot/container.
Each Swoole coroutine gets its own isolated service scope via Coroutine::getContext(). When the coroutine exits, Swoole destroys the context automatically — no manual cleanup required.
Installation
composer require phpdot/container-swoole
Usage
use PHPdot\Container\ContainerBuilder; use PHPdot\Container\Swoole\SwooleContextProvider; use function PHPdot\Container\singleton; use function PHPdot\Container\scoped; $container = (new ContainerBuilder()) ->withContextProvider(new SwooleContextProvider()) ->addDefinitions([ // Shared across all coroutines Router::class => singleton(), Redis::class => singleton(), // Isolated per coroutine — fresh for each request Session::class => scoped(), SignalManager::class => scoped(), ]) ->build();
How It Works
┌──────────────────────────────────────────────────────────┐
│ Swoole Worker │
│ │
│ Singletons (shared) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Router Redis Config LogBridge │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ Coroutine 1 Coroutine 2 │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Session (User A) │ │ Session (User B) │ │
│ │ Signal (trace-1) │ │ Signal (trace-2) │ │
│ └──────────────────┘ └──────────────────┘ │
│ auto-destroyed auto-destroyed │
│ on coroutine exit on coroutine exit │
└──────────────────────────────────────────────────────────┘
Singleton services resolve once and are shared across all coroutines in the worker.
Scoped services resolve once per coroutine and are stored in Swoole\Coroutine::getContext(). When the coroutine finishes, Swoole's runtime destroys the context and all scoped instances are garbage collected.
Outside a coroutine (CLI bootstrap, onStart callback), the provider falls back to an in-memory ArrayContext.
Server Example
use Swoole\Http\Server; use PHPdot\Container\ContainerBuilder; use PHPdot\Container\Swoole\SwooleContextProvider; use function PHPdot\Container\singleton; use function PHPdot\Container\scoped; $container = (new ContainerBuilder()) ->withContextProvider(new SwooleContextProvider()) ->addDefinitions([ Config::class => singleton(), Session::class => scoped(fn($c) => Session::fromRequest($c->get(Request::class))), ]) ->build(); $server = new Server('0.0.0.0', 8080); $server->on('request', function ($req, $res) use ($container) { // Each request runs in its own coroutine. // Scoped services are fresh. Singletons are shared. $session = $container->get(Session::class); $res->end('Hello ' . $session->user()); // Coroutine ends here — scoped instances destroyed automatically. }); $server->start();
Request dispatching
ContainerDispatcher is a PSR-15 handler that resolves your real handler from the container on every request — inside the worker, after the fork. Serve it instead of the handler itself so the request path loads lazily (which keeps it hot-reloadable) and scoped services isolate per coroutine:
use PHPdot\Container\Swoole\ContainerDispatcher; // $container built as above; App\Handler is your PSR-15 entry point. $dispatcher = new ContainerDispatcher($container, App\Handler::class); // Hand $dispatcher to your PSR-15 server (e.g. phpdot/server-swoole's serve()). // On each request it calls $container->get(App\Handler::class) — resolving, // and loading, in the worker.
If the configured id doesn't resolve to a Psr\Http\Server\RequestHandlerInterface, it throws — a fast failure for a misconfigured handler id.
Requirements
- PHP >= 8.3
- ext-swoole >= 5.0
- phpdot/container ^1.0
License
MIT