PHPackages                             phpdot/server-swoole - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. phpdot/server-swoole

ActiveLibrary[HTTP &amp; Networking](/categories/http)

phpdot/server-swoole
====================

Swoole HTTP server adapter for PSR-15. Framework-agnostic.

v3.1.4(1mo ago)036MITPHPPHP &gt;=8.4

Since Apr 1Pushed 1mo agoCompare

[ Source](https://github.com/phpdot/server-swoole)[ Packagist](https://packagist.org/packages/phpdot/server-swoole)[ RSS](/packages/phpdot-server-swoole/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (56)Versions (33)Used By (0)

phpdot/server-swoole
====================

[](#phpdotserver-swoole)

Swoole HTTP/WebSocket server adapter for PSR-15. Framework-agnostic, standalone, full Swoole coverage.

Install
-------

[](#install)

```
composer require phpdot/server-swoole
```

Requires `ext-swoole >= 6.2` and PHP 8.4+.

Quick Start
-----------

[](#quick-start)

```
use Nyholm\Psr7\Factory\Psr17Factory;
use PHPdot\Server\Swoole\SwooleServer;
use PHPdot\Server\Swoole\Config\ServerConfig;

$factory = new Psr17Factory();
$config = new ServerConfig(host: '0.0.0.0', port: 8080, workerNum: 4);

$server = new SwooleServer($factory, $config);
$server->serve($handler);
```

`$handler` is any PSR-15 `RequestHandlerInterface` -- your router, your framework, your middleware pipeline.

---

Server Configuration
--------------------

[](#server-configuration)

`ServerConfig` is a simple readonly data class. No static methods, no builders -- just named constructor parameters:

```
$config = new ServerConfig(
    workerNum: 8,
    maxRequest: 50000,
    daemonize: true,
    pidFile: '/var/run/app.pid',
    logFile: '/var/log/app.log',
);
```

### Workers &amp; Process

[](#workers--process)

```
$config = new ServerConfig(
    workerNum: 8,              // worker processes (default: CPU count)
    taskWorkerNum: 4,          // task workers (default: 0)
    maxRequest: 100000,        // restart worker after N requests
    maxCoroutine: 100000,      // max coroutines per worker
    mode: SWOOLE_PROCESS,      // SWOOLE_PROCESS (default) or SWOOLE_BASE
);
```

### SSL / HTTPS

[](#ssl--https)

```
$config = new ServerConfig(
    port: 443,
    sockType: SWOOLE_SOCK_TCP | SWOOLE_SSL,
    sslCertFile: '/etc/ssl/certs/app.pem',
    sslKeyFile: '/etc/ssl/private/app.key',
    http2: true,
);

$server->serve($handler);
```

### Static Files

[](#static-files)

```
$config = new ServerConfig(
    staticHandler: true,
    documentRoot: '/var/www/public',
    staticHandlerLocations: ['/assets', '/images', '/favicon.ico'],
);
```

Static file requests bypass PHP entirely -- served directly by Swoole's kernel.

### Compression

[](#compression)

```
$config = new ServerConfig(
    httpCompression: true,          // enabled by default
    httpCompressionLevel: 3,        // 1-9 (default: 1)
    httpCompressionMinLength: 20,   // min bytes to compress (default: 20)
);
```

### Raw Swoole Settings

[](#raw-swoole-settings)

For any Swoole setting not covered by typed properties:

```
$config = new ServerConfig(
    workerNum: 4,
    rawSettings: [
        'dispatch_mode' => 2,
        'reload_async' => true,
    ],
);
```

Typed properties always take precedence over `rawSettings`.

### Inside the phpdot framework

[](#inside-the-phpdot-framework)

`ServerConfig` carries `#[Config('server')]`, so when used with `phpdot/package` it's auto-hydrated from `config/server.php`:

```
// config/server.php
return [
    'workerNum'  => 4,
    'maxRequest' => 10000,
    'daemonize'  => false,
    // ... any ServerConfig property
];
```

The container resolves `ServerConfig` automatically — no manual `new ServerConfig(...)` needed when running inside the framework. Standalone consumers (no `phpdot/package`) instantiate `ServerConfig` directly via the constructor as shown above.

`SwooleServer` itself isn't auto-wired (its constructor uses an intersection type for the PSR-17 factory, which PHP-DI can't autowire). Register it manually in your application boot:

```
$builder->register(
    SwooleServer::class,
    new ScopedDefinition(
        scope: Scope::SINGLETON,
        factory: static fn (ContainerInterface $c): SwooleServer => new SwooleServer(
            $c->get(\PHPdot\Http\ResponseFactory::class),  // satisfies all 4 PSR-17 factory interfaces
            $c->get(ServerConfig::class),
        ),
    ),
);
```

For per-coroutine scoping of `Scope::SCOPED` services (the standard pattern under Swoole), install `phpdot/container-swoole` and register its provider:

```
$builder->withContextProvider(new SwooleContextProvider());
```

---

Event Callbacks
---------------

[](#event-callbacks)

Register callbacks directly on the server. Multiple callbacks per event -- they stack, never replace:

```
$server = new SwooleServer($factory, $config);

// Lifecycle
$server->onStart(function (Server $server): void {
    cli_set_process_title('app: master');
});

$server->onWorkerStart(function (Server $server, int $workerId): void {
    cli_set_process_title("app: worker {$workerId}");
});

$server->onShutdown(function (Server $server): void {
    echo "Server stopped\n";
});
```

### Available Events

[](#available-events)

CategoryEventsLifecycle`onStart`, `onManagerStart`, `onManagerStop`, `onWorkerStart`, `onWorkerStop`, `onWorkerExit`, `onWorkerError`, `onBeforeShutdown`, `onShutdown`, `onBeforeReload`, `onAfterReload`Connection`onConnect`, `onClose`Task`onTask`, `onFinish`IPC`onPipeMessage`WebSocket`onOpen`, `onMessage`, `onHandshake`, `onDisconnect`---

WebSocket
---------

[](#websocket)

When any WebSocket callback is registered, the server automatically creates a `WebSocket\Server` instead of `Http\Server`. HTTP and WebSocket work on the same port:

```
$server->onOpen(function (WebSocketServer $server, Request $request): void {
    echo "Client connected: {$request->fd}\n";
});

$server->onMessage(function (WebSocketServer $server, Frame $frame): void {
    $server->push($frame->fd, "Echo: {$frame->data}");
});

$server->onClose(function (Server $server, int $fd): void {
    echo "Client disconnected: {$fd}\n";
});

$server->serve($handler);
```

### Active WebSocket Methods

[](#active-websocket-methods)

Push messages and manage connections from anywhere in your application:

```
$server->push($fd, $data);              // send data to a client
$server->wsDisconnect($fd);             // disconnect a client
$server->isEstablished($fd);            // check if connection is active
```

---

Task Workers
------------

[](#task-workers)

Offload heavy work to task worker processes:

```
$server = new SwooleServer($factory, new ServerConfig(taskWorkerNum: 4));

$server->onTask(function (Server $server, Task $task): void {
    // runs in a task worker process
    $result = processHeavyWork($task->data);
    $task->finish($result);
});

$server->onFinish(function (Server $server, int $taskId, mixed $data): void {
    // result returned to the requesting worker
});

// dispatch from anywhere after serve()
$server->task($data);                          // async dispatch
$server->taskCo([$data1, $data2], timeout: 1); // coroutine dispatch, wait for results
$server->finish($result);                      // return result from task worker
```

---

Timers
------

[](#timers)

Set recurring or one-shot timers:

```
$timerId = $server->tick(5000, function (): void {
    // runs every 5 seconds
});

$server->after(10000, function (): void {
    // runs once after 10 seconds
});

$server->clearTimer($timerId);
```

---

Connection Management
---------------------

[](#connection-management)

```
$server->exists($fd);                     // check if connection exists
$server->close($fd);                      // close a connection
$server->getClientInfo($fd);              // get connection details
$server->getClientList();                 // list connected file descriptors
$server->sendMessage($data, $workerId);   // send message to another worker
```

---

Server Info &amp; Lifecycle
---------------------------

[](#server-info--lifecycle)

```
// info
$server->stats();
$server->getWorkerId();
$server->getWorkerPid();
$server->getWorkerStatus();
$server->getMasterPid();
$server->getManagerPid();

// lifecycle
$server->shutdown();
$server->reload();
$server->stop($workerId);
```

---

Escape Hatch
------------

[](#escape-hatch)

For advanced Swoole features not directly exposed (addProcess, addListener, bind, protect, etc.):

```
$swoole = $server->getServer();
$swoole->addProcess(new Process(function () { /* ... */ }));
```

---

Streaming (CallbackStreamInterface)
-----------------------------------

[](#streaming-callbackstreaminterface)

For real-time streaming (SSE, chunked responses), implement `CallbackStreamInterface`:

```
use PHPdot\Server\Swoole\Contract\CallbackStreamInterface;

final class SseStream implements StreamInterface, CallbackStreamInterface
{
    public function __construct(private readonly Closure $producer) {}

    public function getCallback(): Closure
    {
        return function (Closure $write): void {
            ($this->producer)($write);
        };
    }
}
```

The `ResponseConverter` detects this interface and streams each chunk directly via `$swooleResponse->write()` -- data reaches the client immediately without buffering.

---

Error handling
--------------

[](#error-handling)

Handling application errors is your middleware's job, not the adapter's. If a handler throws and nothing catches it, the server sends a last-resort `500` carrying the exception message and keeps the worker alive — so for safe, consistent error responses, put error-handling middleware at the top of your PSR-15 stack (e.g. `phpdot/error-handler`) to catch exceptions before they reach the transport.

---

Architecture
------------

[](#architecture)

 ```
flowchart TB
    Client(["Client"])
    Server["Swoole HTTP / WS Server"]
    ReqConv["RequestConverterSwoole → PSR-7headers, URI, body, cookies, files"]
    Handler["Your PSR-15 Handlerrouter, middleware, controllers"]
    ResConv["ResponseConverterPSR-7 → Swoolesendfile, chunked, streaming"]

    Client --> Server
    Server -- "Swoole\Http\Request" --> ReqConv
    ReqConv -- "ServerRequestInterface" --> Handler
    Handler -- "ResponseInterface" --> ResConv
    ResConv -- "Swoole\Http\Response" --> Server
    Server --> Client
```

      Loading ### Response Emission Strategies

[](#response-emission-strategies)

The `ResponseConverter` selects the optimal strategy for each response:

StrategyWhenHowCallbackStreamBody implements `CallbackStreamInterface``write()` per chunk -- true streamingSendfileBody is a plain file stream`sendfile()` -- zero-copy kernel transferEmptyBody size is 0`end()` -- no bodyChunkedBody exceeds chunk threshold (default 1 MB)`write()` in chunksDirectEverything else`end($body)` -- single write---

Production Example
------------------

[](#production-example)

```
$config = new ServerConfig(
    host: '0.0.0.0',
    port: 443,
    workerNum: 8,
    taskWorkerNum: 2,
    maxRequest: 100000,
    daemonize: true,
    pidFile: '/var/run/app.pid',
    logFile: '/var/log/app.log',
    logLevel: SWOOLE_LOG_WARNING,
    sockType: SWOOLE_SOCK_TCP | SWOOLE_SSL,
    sslCertFile: '/etc/ssl/certs/app.pem',
    sslKeyFile: '/etc/ssl/private/app.key',
    http2: true,
    httpCompression: true,
    staticHandler: true,
    documentRoot: '/var/www/public',
);

$server = new SwooleServer($factory, $config);

$server->onWorkerStart(function (Server $server, int $workerId): void {
    cli_set_process_title("app: worker {$workerId}");
});

$server->serve($handler);
```

---

Development hot reload
----------------------

[](#development-hot-reload)

Swoole keeps code resident, so edits don't apply until the workers reload — and `reload()` only reloads code loaded **after** the worker fork (master/bootstrap code needs a full restart). The watcher makes that split explicit.

Build a `Watcher` and hand it to `watch()` before `serve()`. With no arguments it watches the current working directory, skips `vendor`/`.git`, and reloads on `.php` changes — narrow it with the constructor (typically wired from your CLI `--watch` flags):

```
use PHPdot\Server\Swoole\Watch\Watcher;

$server->watch(new Watcher(
    paths: ['/app/src'],          // empty = current working directory
    extensions: ['php', 'twig'],  // empty = ['php']
    excludes: ['vendor', '.git'], // empty = ['vendor', '.git']
    restart: ['config'],          // path segments that need a full restart, not a reload
));
$server->serve($handler);
```

On change the watcher reloads workers (`[watch] reloaded: ...`), except files matching a `restart` segment — those load before the fork, so they only get a notice (`[watch] restart required: ...`) and the full restart is yours to do. Development only — never attach it in production.

For rules a flag can't express, implement `WatcherInterface` and pass it to `watch()`; `Watcher` is just the default implementation.

Lifecycle listeners
-------------------

[](#lifecycle-listeners)

Hook the server lifecycle with classes, not closures. Implement any of the `Contract\Event\*` interfaces and hand the listener to `subscribe()` before `serve()` — each interface it implements is wired onto the matching event:

```
use PHPdot\Server\Swoole\Contract\Event\OnWorkerStartInterface;
use PHPdot\Server\Swoole\Contract\Event\OnShutdownInterface;
use PHPdot\Server\Swoole\SwooleServer;

final class ServerKernel implements OnWorkerStartInterface, OnShutdownInterface
{
    public function onWorkerStart(SwooleServer $server, int $workerId): void
    {
        // runs on every worker (re)start — including after a reload.
        // the place for opcache_reset(), cache warming, connection setup.
    }

    public function onShutdown(SwooleServer $server): void
    {
        // master shutting down — flush and clean up.
    }
}

$server->subscribe(new ServerKernel());
$server->serve($handler);
```

Implement only the events you need — each lifecycle event is its own small interface: `OnStartInterface`, `OnManagerStartInterface`, `OnManagerStopInterface`, `OnWorkerStartInterface`, `OnWorkerStopInterface`, `OnWorkerExitInterface`, `OnWorkerErrorInterface`, `OnBeforeReloadInterface`, `OnAfterReloadInterface`, `OnBeforeShutdownInterface`, `OnShutdownInterface`. Listeners stack, so the framework's callbacks and yours coexist.

The I/O events (`onMessage`, `onOpen`, `onTask`, `onConnect`, …) are deliberately **not** listeners — they belong to the handler (`WebSocketHandlerInterface`, the task handler), not the lifecycle.

Package Structure
-----------------

[](#package-structure)

```
src/
  SwooleServer.php                Main entry point -- events, active methods, lifecycle
  Config/
    ServerConfig.php              Readonly server configuration
  Contract/
    CallbackStreamInterface.php   Streaming contract
    WatcherInterface.php          Dev file-watch policy (paths, extensions, depth, classify)
    Event/                        11 lifecycle listener interfaces (OnStart, OnWorkerStart, OnShutdown, ...)
  Converter/
    RequestConverter.php          Swoole -> PSR-7
    ResponseConverter.php         PSR-7 -> Swoole
  Enum/
    WatchAction.php               Reload | Restart | Ignore
  Exception/
    ServerException.php           Server errors
  Watch/
    FileWatcher.php               Polling hot-reload engine
    Watcher.php                   Configurable WatcherInterface (built from watch flags)

```

PSR Standards
-------------

[](#psr-standards)

PSRUsagePSR-7`ServerRequestInterface`, `ResponseInterface` -- the bridge formatPSR-15`RequestHandlerInterface` -- your application entry pointPSR-17All 4 factories -- builds PSR-7 objects from Swoole dataDevelopment
-----------

[](#development)

```
composer test        # PHPUnit
composer analyse     # PHPStan level 10
composer cs-fix      # PHP-CS-Fixer
composer check       # All three
```

License
-------

[](#license)

MIT

###  Health Score

46

—

FairBetter than 92% of packages

Maintenance91

Actively maintained with recent releases

Popularity10

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity63

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

32

Last Release

42d ago

Major Versions

v1.0.2 → v2.0.02026-04-14

v2.9.0 → v3.0.02026-06-27

PHP version history (2 changes)v1.0.0PHP &gt;=8.3

v3.0.6PHP &gt;=8.4

### Community

Maintainers

![](https://www.gravatar.com/avatar/62e82421bda4b5d6ba9a47ba6d88caca060dcd0d1a2862f351f3a97657385db0?d=identicon)[phpdot](/maintainers/phpdot)

---

Top Contributors

[![phpdot](https://avatars.githubusercontent.com/u/252500?v=4)](https://github.com/phpdot "phpdot (33 commits)")

---

Tags

httppsr-7asyncserverpsr-15swoole

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/phpdot-server-swoole/health.svg)

```
[![Health](https://phpackages.com/badges/phpdot-server-swoole/health.svg)](https://phpackages.com/packages/phpdot-server-swoole)
```

###  Alternatives

[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

7.9k1.1B4.4k](/packages/guzzlehttp-psr7)[cakephp/cakephp

The CakePHP framework

8.9k20.0M1.8k](/packages/cakephp-cakephp)[sunrise/http-router

A powerful solution as the foundation of your project.

16852.3k12](/packages/sunrise-http-router)[typo3/cms

TYPO3 CMS is a free open source Content Management Framework initially created by Kasper Skaarhoj and licensed under GNU/GPL.

1.2k1.9M122](/packages/typo3-cms)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k20](/packages/tempest-framework)[mezzio/mezzio

PSR-15 Middleware Microframework

3973.9M133](/packages/mezzio-mezzio)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
