lawoole/framework
Lawoole Framework is a high-performance PHP framework built on Swoole, bringing coroutine-based concurrency to Laravel-style conventions. It offers routing, middleware, ORM integration and familiar ergonomics for building scalable APIs and long-running web services in PHP.
Read this first: lawoole/framework last shipped in January 2019, has 32 stars and zero dependents. It is a Swoole-resident fork of the Laravel stack (container, routing, console, facades) with an HTTP/WebSocket server on top. Treat it as a legacy runtime you inherit, not a stack you pick in 2024. Before writing a line of feature code, spend 30 minutes deciding whether you are maintaining it or replacing it — Laravel Octane, Hyperf, or a plain Swoole server will cost less than debugging a PHP 7.3/Swoole 4.x toolchain for the next year. If you must proceed:
composer create-project lawoole/lawoole myapp gives you the lawoole console binary, config/, routes/, and app/ — the same layout as a Laravel app. Only composer require lawoole/framework if the skeleton repo is gone; you will then have to hand-build the entry script.config/server.php (host, port, swoole.options such as worker_num, daemonize, max_request). This file, plus src/Server/HttpServer.php and src/Application.php, is where 90% of your operational questions are answered.php lawoole list to see the actual command names in your version, then start the HTTP server with daemonize off, and curl it. Confirm an empty Laravel-style route returns before touching a database.routes/http.php exactly as you would in Laravel, point it at a controller, and return an array or a response object. Then — and only then — add one coroutine-aware MySQL or Redis call, because that is the step where the framework's assumptions differ from Laravel's.Where to look when something is odd: src/Server/ for the request lifecycle and worker callbacks, src/Routing/ for dispatch, src/Coroutine/ and src/Swoole/ for the client wrappers, and storage/logs/ for anything swallowed inside a coroutine.
Boot once, serve thousands — design for it. The container is built per worker, not per request. Anything you singleton() outlives the request that created it. Default to bind(); reserve singletons for genuinely stateless services (config readers, pure helpers). If you must cache request-derived data on a singleton, key it by coroutine ID and clear it in terminating middleware.
Reset request-scoped state in middleware. auth, session, request, and URL-generator state are the classic leak points. Write one middleware at the front of the stack that captures the current request object explicitly (pass $request down rather than relying on a cached request() helper) and one at the end that flushes the scoped instances.
Parallelize I/O with coroutines, not with forks. Sequential queries are the main performance loss in this style of framework. Use Coroutine::create plus a Channel, or a WaitGroup, to fan out independent queries; use the framework's coroutine MySQL/Redis clients so a slow query yields instead of blocking the worker. A single blocking PDO, file_get_contents, or curl_exec in a handler stalls every request on that worker — this is the number one production symptom (timeouts and 502s under load with low CPU).
Share state across workers deliberately. Worker memory is not shared. Use Swoole\Table for hot per-node counters, Redis for anything durable, and task_worker_num plus Server::task() for fire-and-forget jobs (mail, webhooks, log shipping). Never assume a WebSocket connection in worker 3 is visible from worker 7 — for rooms and presence, key subscriptions in Redis or a Table.
Keep WebSocket handlers thin. Implement the handler contract with onOpen / onMessage / onClose, decode once, dispatch to a service class, and encode once. Because onMessage runs inside a coroutine, any exception there can vanish silently — wrap the body in try/catch and log.
Use the console for everything that is not a request. Migrations, seeders, scheduled jobs, and one-off scripts run through the lawoole binary without the server, so they behave like normal Laravel console commands. Keeping back-office work out of the HTTP runtime is the cheapest way to avoid state bugs.
Make controllers portable. Keep business logic behind framework-agnostic service classes and inject them. Given the package's age, a future port to Octane or Hyperf is likely; if controllers only touch $request and return arrays, that port is a week, not a quarter.
Testing. Unit-test services and controllers by booting the application container without starting the server — no Swoole needed. Keep a small integration suite that hits a foreground server with an HTTP client for the coroutine/client code paths, and run it in CI on the pinned PHP/Swoole versions only.
$_GET, $_POST, $_SERVER, and header() do not describe the current request. Always take the framework request object; library code that reads superglobals (many Laravel-era packages do) will silently misbehave.echo, var_dump, and die corrupt responses. Output goes to worker stdout, not the client, and exit kills the worker mid-request. Return a response object and let the server write it.reload or restart. Workers keep the old opcode until recycled; if you enable opcache for CLI, remember opcache.validate_timestamps behavior. Set max_request as a safety valve so workers recycle periodically and slow leaks do not become OOM kills.memory_get_usage() every N requests in a middleware; it should be flat. Growth almost always means a singleton holding a request, a closure capturing $this from a request-scoped object, or an ever-growing static array.daemonize => false, log to stdout, and tail storage/logs/. For hung requests, dump coroutine backtraces to find the blocking call.Coroutine::create / onMessage body, or you will debug "nothing happened" for hours.Lawoole\Server\Server for a custom protocol server, register listeners against the underlying Swoole server callbacks for connection lifecycle events, and add custom route dispatchers or middleware for cross-cutting concerns. These are stable parts of the codebase; the framework internals around container scoping are the fragile parts.How can I help you explore Laravel packages today?