Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Swoole Patterns Laravel Package

small/swoole-patterns

Swoole patterns for PHP: coroutine-friendly abstractions and reusable building blocks for running Laravel or plain PHP applications on the Swoole extension, with practical guidance on common server and concurrency patterns.

View on GitHub
Deep Wiki
Context7

Getting Started

small/swoole-patterns is a low-level library: it gives you reusable async building blocks (pools, channels, deferreds, timeout/retry wrappers) on top of the OpenSwoole runtime rather than Laravel-native abstractions. Treat it as infrastructure you wire into a worker bootstrap, not something you sprinkle through controllers.

Prerequisites

  • PHP 8.1+ with the openswoole (or swoole) extension compiled in — verify with php -m | grep -i swoole and php --ri openswoole.
  • Coroutines enabled (openswoole.enable_coroutine=On, openswoole.use_shortname=Off recommended).
  • A private repository entry in composer.json before composer require small/swoole-patterns, since the package lives on git.small-project.dev and is not on Packagist:
"repositories": [
  { "type": "vcs", "url": "https://git.small-project.dev/lib/small-swoole-patterns" }
]

Pin an explicit version or commit ("small/swoole-patterns": "dev-main#<sha>") — with zero dependents and no release history to lean on, floating branches are a real risk.

Where to look first, in order

  1. composer.json — PHP/Swoole version constraints and autoload map (Small\SwoolePatterns\...). This tells you which patterns are actually shipped.
  2. src/ — one directory per pattern. Read the interfaces first; they define the contracts you'll type-hint.
  3. tests/ — the fastest readable documentation of intended lifecycle (start/stop, channel close semantics, pool checkout/checkin).
  4. README.md — usually thin in small libs; do not assume it covers failure modes.

First use case

The lowest-risk, highest-value entry point is a bounded coroutine pool around a slow external dependency. Start there: create the pool during WorkerStart, push work into it from a coroutine-safe handler, and confirm concurrency is capped and connections are reused. Once that behaves, expand to channels for fan-out work and the timeout/retry decorators around remote calls.

Implementation Patterns

Bootstrap once per worker, not per request. Instantiate pools, channel registries, and circuit-breaker state inside the WorkerStart callback and store them in a worker-scoped container (or a Coroutine::getContext()-aware binding). Anything created per-request defeats the purpose and leaks FDs.

$server->on('WorkerStart', function ($server, int $workerId) use ($container) {
    $container->singleton(Pool::class, fn () => new Pool(
        factory: fn () => new Redis(),
        size: 32,
        timeout: 0.5,
    ));
});

Pool-as-decorator around existing clients. Keep your existing Laravel Redis/HTTP bindings and wrap the connection factory in the pool rather than rewriting call sites. This keeps the pattern library at the edge of your codebase and your domain code unaware of Swoole.

Channel-based fan-out for I/O-bound aggregation. When a request needs N independent remote calls, push them onto a channel and consume with a fixed number of coroutine consumers sized to the pool. This gives you backpressure for free — bounded channel capacity is your concurrency limit, and it fails loudly instead of exhausting memory.

Timeout and retry as decorators, not inline code. Wrap remote calls in the package's timeout/retry constructs at the boundary layer (repositories, clients). This keeps retry policy auditable in one place and prevents the common bug of nested retries multiplying call volume.

Circuit breaker around unstable dependencies. Put a breaker in front of any third-party API called from a coroutine. Because coroutine state is per-request, the breaker's shared state must be worker-scoped or stored in Swoole Table/Redis — otherwise every coroutine sees its own pristine breaker and it never opens.

Async event dispatch from HTTP handlers. For fire-and-forget work (audit logs, webhooks), push to a channel or Task and return the response immediately. Never go() a closure that writes to the request object — the request is gone by the time it runs.

Octane integration. If you run laravel/octane with the OpenSwoole server, place pattern initialization in a listener on Octane\Events\WorkerStarting, and flush/reset anything holding request state on RequestTerminated. Treat Octane's worker lifecycle as the contract the package expects.

Lifecycle symmetry. Every start()/acquire() needs a matching stop()/release() in WorkerStop and in exception paths. Wrap pool checkout in try/finally — a leaked lease silently shrinks your pool until it deadlocks, and the symptom (hanging requests, no errors) is hard to trace back.

Gotchas and Tips

Static state is per-worker, not per-process. Singletons, counters, and caches persist across requests within a worker. Any mutable state in a pattern implementation must be reset between requests or explicitly designed to be shared. This is the #1 source of "works in php artisan serve, broken under Swoole" bugs.

Blocking calls freeze the whole worker. A single sleep(), file_get_contents(), or non-coroutine PDO call blocks every coroutine on that worker. Enable Swoole's runtime hooks (OpenSwoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL)) early in bootstrap and audit for libraries that bypass hooks (some MySQL clients do).

Never share a connection across coroutines. One Redis/PDO handle used by two concurrent coroutines interleaves protocol frames and returns mismatched responses. The pool exists precisely to prevent this; don't defeat it by caching a client in a property.

Channel capacity is a trap in both directions. Unbuffered channels block the producer until a consumer arrives (deadlock if the consumer is the same coroutine). Overly large buffers hide backpressure problems until memory spikes. Start small (pool size or 2×) and tune with metrics.

Debuggability is poor out of the box. Stack traces from coroutines are shallow and dd()/var_dump() inside a coroutine can corrupt output or hang. Use file logging with Coroutine::getCid() in every line, and prefer Coroutine::getBackTrace() over debug_print_backtrace().

Config caching and env reads. config:cache is incompatible with runtime env() calls in worker bootstrap if you cache before env is populated. Read config values during bootstrap and pass them as constructor arguments; avoid env() in WorkerStart.

Testing outside a coroutine context fails. Unit tests calling pool or channel methods without an active coroutine will throw or silently misbehave. Run pattern-level tests inside Coroutine::create() / run(), or use Swoole's Coroutine\Test-style harness. Laravel's standard RefreshDatabase transactional tests do not compose well with pooled connections — isolate DB-touching pattern tests.

Dependency risk is real. Zero stars, zero dependents, and a non-Packagist host mean no community validation of edge cases and no guaranteed maintenance. Before adopting broadly: read the test suite coverage, check the last commit date, vendor the critical patterns (a pool and a channel are ~100 lines each) if the API surface you need is small, and wrap every interaction behind an interface so you can swap the implementation later.

Extension points to prefer. Favor the package's interfaces (pool factory, channel, breaker storage) over its concrete classes when type-hinting in your own code. That's your escape hatch if you need to replace a pattern with an in-house implementation or with openswoole/library equivalents later.

Version pinning and lock discipline. With no upstream ecosystem, a force-push or branch rewrite silently changes your runtime behavior. Commit composer.lock, pin to a SHA, and re-run your coroutine test suite on every bump.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle