swlib/util
swlib/util is a lightweight PHP utility package for Laravel and standalone projects, offering reusable helpers that reduce boilerplate and simplify common development tasks. Its simple API makes it easy to drop in and keep code clean, consistent, and readable.
swlib/util is a small utility belt that sits on top of the Swoole extension. It does not replace Swoole — it wraps the rough edges of coroutines, wait groups, process handling, and shared context so that ordinary PHP code (including Laravel code you’re porting into a Swoole worker) can be written without touching Swoole\Coroutine\* directly.
Minimal steps:
composer require swlib/util:^1.0
php -m | grep -i swoole # must show "swoole"
php -r 'echo SWOOLE_VERSION, PHP_EOL;'
Requirements to check before anything else:
| Requirement | Why it matters |
|---|---|
ext-swoole >= 4.4 (ideally 4.8+) |
The library predates Swoole 5; several coroutine APIs were deprecated/renamed |
| PHP 7.2–7.4 | Last release 2020-08-26; PHP 8 support is not guaranteed |
| CLI-only usage | These utilities assume Swoole\Runtime::enableCoroutine() and a running Swoole server/process |
Where to look first: open vendor/swlib/util/src/ and read the class list. The package is tiny (a handful of files), there is no documentation site worth trusting, and the test suite is thin — the source is the documentation. Read the constructor signatures of the wait-group/coroutine/process classes before designing around them.
The first use case you will hit in real life is fan-out I/O inside a Swoole worker: you have 30 independent HTTP/DB calls that currently run sequentially and you want them concurrent without leaking coroutines. A wait-group primitive is exactly the right tool:
use Swlib\Util\WaitGroup;
use Swoole\Coroutine;
$wg = new WaitGroup();
foreach ($userIds as $id) {
$wg->add();
Coroutine::create(function () use ($wg, $id) {
try {
$profile[$id] = fetchProfile($id); // hooked I/O
} finally {
$wg->done(); // always done, even on throw
}
});
}
$wg->wait();
If that compiles and returns in roughly the time of the slowest call rather than the sum of all calls, you have validated the package end-to-end.
1. Fan-out / fan-in with a WaitGroup. The canonical pattern above. Always pair add() with a finally { done(); }; a single uncaught exception without done() deadlocks the whole worker on wait(). Keep the $wg object scoped to a request/job, never in a static property.
2. Bounded concurrency with a channel as a semaphore. Unbounded Coroutine::create in a loop will exhaust the worker pool or the remote service. Throttle explicitly:
$limit = new \Swoole\Coroutine\Channel(16);
foreach ($urls as $url) {
$limit->push(true); // blocks at 16
\Swoole\Coroutine::create(function () use ($limit, $url, &$out) {
try { $out[$url] = httpGet($url); }
finally { $limit->pop(); }
});
}
The utility package does not give you a scheduler — this is still your job.
3. Wrap coroutine spawns in one place. Don’t scatter Coroutine::create across controllers and jobs. Put a small CoroutineRunner service in Laravel’s container, have it own the wait group and the semaphore, and let callers hand it callables. When Swoole’s API shifts (and it does), you change one file.
4. Process helpers for CPU-bound work. If a utility process class is present in src/, use it for tasks that would block the event loop (image resizing, PDF generation, large JSON transforms) rather than wrapping them in coroutines. Coroutines cooperate; they don’t preempt. A for loop over 10M rows inside a coroutine stalls the entire worker just as badly as in fpm.
5. Context propagation. Per-coroutine context is the single most valuable utility in this family: correlation IDs, tenant IDs, current user, trace spans. Set it at the entry point of a request/job, read it in the depths of your service layer, and make sure any helper you write (logger processors, DB query listeners) reads from it rather than from $_SERVER or a request singleton.
6. Integration with Laravel. There is no service provider. Register the bindings yourself in an app-level provider, and make sure the provider is only loaded in the Swoole/Octane-style entrypoint so the package never gets pulled into fpm requests where Swoole\Coroutine is unavailable.
7. Testing. Unit-test pure logic in normal PHPUnit. For anything asserting on coroutine behaviour, run the test suite inside Swoole\Coroutine\run(fn() => ...) or inside a test server process; a plain phpunit invocation will throw "API must be called in the coroutine" and you’ll waste an afternoon.
composer.json and consider copying the one or two classes you actually use into app/Support/.Coroutine::getuid() is deprecated in favour of getCid(), Swoole\Runtime::enableCoroutine() semantics changed, and several Channel/Coroutine behaviours were tightened. If a class blows up on Swoole 5, it’s usually a one-line rename — but you’ll be patching vendor code, so fork it.wait() deadlocks silently. If any branch throws before done(), the worker hangs with no error. Wrap every unit of work in try/finally and add a defer-style helper; also set a channel timeout on your semaphore so a stuck coroutine surfaces as an exception instead of a frozen process.wait(). Don’t rely on the outer try/catch to see them.PDO is fine only when runtime hooks are enabled; shell_exec, sleep (non-hooked), and heavy CPU loops are not. Audit for them before blaming the library.finally at the worker request boundary.swlib/saber and swlib/http-client are from the same era and have the same maintenance profile; if you adopt one, adopt them all at pinned versions, or prefer a modern client.Swoole\Coroutine\WaitGroup, or Octane’s concurrency facade) is a swap, not a rewrite.How can I help you explore Laravel packages today?