workerman/webman-framework
Webman is a high-performance PHP framework built on Workerman for fast, scalable web apps and APIs. It supports HTTP, WebSocket, TCP, and other protocols, with simple routing, middleware, low overhead, and persistent memory.
workerman/webman-framework is the core package, not the thing you normally install by hand. Day-to-day you get it transitively from the skeleton:
composer create-project workerman/webman myapp
cd myapp
php start.php start # foreground, logs to stdout
Only add it explicitly when you are embedding webman into an existing Workerman/TCP project.
Read these first, in order:
start.php — the bootstrap; every run/stop/reload command goes through it.public/index.php — thin HTTP entry.config/ — app.php, route.php, middleware.php, process.php, database.php, log.php.app/controller/IndexController.php — the generated hello-world.support/ — helpers (response(), json(), view(), config(), env()), the container, bootstrap.First real use case — a JSON endpoint:
// config/route.php
use Webman\Route;
Route::get('/health', fn () => json(['ok' => true, 'ts' => time()]));
Route::post('/users', [app\controller\UserController::class, 'store']);
namespace app\controller;
use support\Request;
class UserController
{
public function store(Request $request)
{
$data = $request->post();
// ...
return json(['id' => 1], 201);
}
}
php start.php start
curl localhost:8787/health
If you edit a file you must php start.php reload (or run with php start.php start -d plus a watcher). There is no FPM-style per-request reload — this is the single biggest behavioral difference coming from Laravel.
Controllers are resolved per request, but the process is not. Instantiate nothing at property level that holds request state.
Route organization. Keep config/route.php thin and split into route/*.php files as the app grows; webman loads them all. Controller auto-routing (app/controller/Foo/BarController::baz → /foo/bar/baz) is convenient but makes URLs implicit — prefer explicit routes for anything public.
Middleware is the main cross-cutting seam:
namespace app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use support\Request;
class Auth implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
if (!$request->header('authorization')) {
return json(['error' => 'unauthorized'], 401);
}
return $handler($request); // must return the Response
}
}
Register globally in config/middleware.php, per-route/group in config/route.php, or per-controller via annotations. Order is array order, outermost first.
Database. illuminate/database and Eloquent ship in the framework:
use support\Db;
$users = Db::table('users')->where('active', 1)->limit(20)->get();
Db::transaction(function () use ($data) { /* ... */ });
Models extend support\Model (or Illuminate\Database\Eloquent\Model). Configure pools/connections in config/database.php; under a coroutine runtime (swow/swoole) this is where you tune pool size.
Config and env. config('app.debug'), env('APP_ENV'), .env at project root. Config is loaded once at boot into memory — mutating config at runtime does not persist across workers and confuses everyone; use it read-only.
Background work. Long-running or scheduled jobs do not belong in a controller. Add custom processes in config/process.php (each gets its own worker loop with onWorkerStart), or use the webman/cron and webman/redis-queue plugins. This is where webman genuinely beats FPM: a resident process with a timer, no cron daemon juggling.
Plugins. composer require webman/redis etc.; plugin config lands in config/plugin/<vendor>/<name>/. webman/console gives you php webman make:controller User so you don't hand-write namespaces.
Deployment shape. Nginx (or Caddy) reverses / to 127.0.0.1:8787; static files served by Nginx from public/. Run under supervisor/systemd with php start.php start -d. Deploy = rsync code → composer install --no-dev → php start.php reload. reload is graceful and keeps in-flight requests alive; restart is not.
Testing. Unit-test services and domain code with PHPUnit normally; for HTTP-level tests, boot the app once and drive requests through the container rather than relying on $_SERVER/$_GET, which are not the source of truth here.
.env does nothing to the running process. php start.php reload after code/config changes. New or changed config/process.php entries need restart, not reload.$GLOBALS, and container singletons persist. A singleton that captures $request is a leak and a cross-request data bleed. Clear request-scoped caches in middleware try/finally.sleep(), heavy synchronous HTTP, big file reads inside a handler stall every request that worker would have served. Use async clients (workerman/http-client, Guzzle only if you accept the cost), or switch to a coroutine runtime if you need real concurrency.exit / die kills the worker. The master respawns it, but in-flight requests are dropped. Throw exceptions and let the framework render the error page.$_GET, $_POST, $_SERVER, $_SESSION are not per-request state. Always use $request->get(), ->post(), ->header(), ->file(), ->session().header(), setcookie(), session_start(), ob_start() do not apply. Return a Response: response()->withHeader(...), response()->cookie(...), $request->session()->set(...). Stray echo before returning a Response can corrupt output.sudo; runtime/logs and runtime/session then fail silently-ish. Check php start.php status and the log dir ownership first when "nothing happens."config/session.php; otherwise users bounce between nodes.pcntl/signals. start -d, reload, status are limited or absent. Use WSL/Docker so behavior matches production.workerman/webman-framework and workerman/workerman must be upgraded together; a composer update that bumps only one is a classic boot failure. Pin both in composer.json.'debug' => true in config/app.php gives stack traces — great locally, unacceptable in prod.walkor/webman and the webman documentation site. Searching this repository for "how do I…" answers will mostly waste time; search the parent project and the plugin ecosystem.support\Container bindings for swapping implementations (repositories, mailers) in tests; config/middleware.php ordering for global behavior; config/process.php for anything long-running; events (Event::emit) for decoupling side effects from controllers.How can I help you explore Laravel packages today?