Framework integration
Winter Thread spawns a PHP CLI process per task. Inside a framework or a web SAPI, you usually need to tell it which PHP to run, where the child bootstrap lives, and how to sign payloads. All of that lives on a Launcher you bind once at application startup.
Bind the launcher at bootstrap
Configuration goes through a single seam: Thread::bindLauncher(). Call it once, early — in a
service provider’s boot(), a container bootstrap, or any file every process loads (including
Swoole workers). Every Thread::start() afterwards uses the bound launcher.
The zero-config default is AdaptiveLauncher: on every
launch it looks at the runtime and routes to SwooleLauncher (inside a coroutine) or
CliLauncher (plain CLI, FPM), resolving the binary, the wRunner path and the secret for you.
If you bind nothing, Thread::launcher() lazily creates one — so you only bind when you need an
override.
<?php
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
// Explicitly what happens with no configuration at all.
Thread::bindLauncher(AdaptiveLauncher::adaptive());To override just one aspect, pass the named argument — launchers are immutable
(final readonly), so a fresh one is constructed:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
Thread::bindLauncher(AdaptiveLauncher::adaptive(
secret: $_ENV['APP_SECRET'],
binaryPath: '/usr/bin/php',
));bindLauncher is the only binding
Thread::bindLauncher() replaces the per-setting binders. There is no bindBinaryPath(),
bindRunner(), bindSerSecurity(), or bindPayloadMode() — every one of those is now a
property of the launcher. Rebinding a fresh launcher is how you “reset” to defaults.
The transport belongs to CliLauncher only
AdaptiveLauncher::adaptive() has no transport argument: under Swoole, SwooleLauncher picks
delivery itself (shared memory with ext-shmop, a temp file otherwise). An explicit transport
only makes sense on the CLI/FPM path — bind CliLauncher::adaptive(transport: …) there.
Set the PHP binary under FPM
Under PHP-FPM or CGI, the running interpreter is the web handler, not a CLI binary that can
spawn background workers. CliLauncher already handles this: under a non-CLI SAPI it resolves
PHP_BINDIR/php (falling back to php on PATH), so the common case needs no config. Override
it only when the CLI binary lives elsewhere:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
Thread::bindLauncher(AdaptiveLauncher::adaptive(binaryPath: '/usr/bin/php'));For an explicit, environment-independent config, build a
CliLauncher with its constructor, setting each part:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\CliLauncher;
use Flytachi\Winter\Thread\Payload\TempFileTransport;
Thread::bindLauncher(new CliLauncher(
binaryPath: '/usr/bin/php',
runnerPath: __DIR__ . '/vendor/flytachi/winter-thread/wRunner',
transport: new TempFileTransport(),
));Symptom of a wrong binary
If tasks silently fail to start from a web request, a wrong binary path is the usual cause — the child was launched under the FPM SAPI instead of CLI. Check the binary path first.
Point to the packaged runner script
The child is bootstrapped by the packaged wRunner script (not wExecutor — that name is
gone). The launcher finds it automatically. If your deployment relocates vendor/ or you ship a
custom bootstrap, set the path explicitly:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
Thread::bindLauncher(AdaptiveLauncher::adaptive(
runnerPath: __DIR__ . '/vendor/flytachi/winter-thread/wRunner',
));A custom runner bootstrap is a child-side concern independent of the launcher; if you replace it you typically also replace the launcher. See Runner lifecycle.
Sign payloads app-wide
opis/closure serializes and (optionally) signs tasks so closures and anonymous classes can
cross the process boundary. Set a secret app-wide and forged or tampered payloads are rejected
in the child before any object is built. Two equivalent ways to supply it:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\AdaptiveLauncher;
// 1. The launcher's named argument
Thread::bindLauncher(AdaptiveLauncher::adaptive(secret: $_ENV['APP_SECRET']));
// 2. No code at all — set the WINTER_THREAD_SECRET env var and the launcher reads it.The secret reaches the child through the WINTER_THREAD_SECRET environment variable
(owner-only), never argv — so signing works just as well when the secret is passed
explicitly. Details in Security & performance.
Swoole: the transport is auto-selected
You do not need to detect Swoole yourself. Under an active runtime (inside a coroutine, or with
runtime hooks enabled) AdaptiveLauncher routes the launch to SwooleLauncher, which delivers
the payload pipe-free — shared memory with ext-shmop, a temp file otherwise. A raw stdin pipe
would not do there: it is not coroutine-safe.
Forcing a specific transport is possible only on the CLI/FPM path, on CliLauncher:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Launch\CliLauncher;
use Flytachi\Winter\Thread\Payload\ShmTransport;
// Force shared-memory delivery (requires ext-shmop).
Thread::bindLauncher(CliLauncher::adaptive(transport: new ShmTransport()));The transport trade-offs are in Swoole & payload delivery and Payload modes.
Advanced: build a worker pool without the Thread facade
Framework code that manages its own pool can reach the launcher and drive it directly, one
ProcessHandle per worker instead of a Thread object per task. Serialize the task with the
launcher’s security provider, build a LaunchSpec, and harvest with a non-blocking loop:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\LaunchSpec;
$launcher = Thread::launcher();
$payload = \Opis\Closure\serialize($runnable, $launcher->security());
$handle = $launcher->launch(new LaunchSpec(
payload: $payload,
namespace: 'Pool',
name: 'MyTask',
));
// non-blocking: true once finished (and reaped), false while still running
if ($handle->reap()) {
$exit = $handle->getExitCode();
}Because reap() and detach() never block on a live process, one loop can manage hundreds of
handles without zombies. Full signatures for LaunchSpec, Launcher, and ProcessHandle are
in the API reference.
Related
- API reference —
Launcher, the three launchers,LaunchSpec,ProcessHandle - Swoole & payload delivery — why the transport auto-switches
- Payload modes — Pipe / TempFile / Shm transports
- Security & performance — signing and the secret channel