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 one Engine you bind once at application startup.
Bind the engine at bootstrap
Configuration goes through a single seam: Thread::bindEngine(). 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 engine.
The zero-config default is AdaptiveEngine: it detects
the environment and resolves the transport, PHP binary, wRunner path, and secret for you. If
you bind nothing, Thread::engine() lazily creates one — so you only bind when you need an
override.
<?php
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
// Zero-config: detects binary, transport and secret from the environment.
Thread::bindEngine(new AdaptiveEngine());To override just one aspect, pass the named argument — AdaptiveEngine is immutable, so
construct a fresh one:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
Thread::bindEngine(new AdaptiveEngine(
secret: $_ENV['APP_SECRET'],
binaryPath: '/usr/bin/php',
));bindEngine is the only binding
Thread::bindEngine() replaces the old per-setting binders. There is no bindBinaryPath(),
bindRunner(), bindSerSecurity(), or bindPayloadMode() — every one of those is now a
property of the engine. Rebinding a fresh engine is how you “reset” to defaults.
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. AdaptiveEngine 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\Engine\AdaptiveEngine;
Thread::bindEngine(new AdaptiveEngine(binaryPath: '/usr/bin/php'));For an explicit, environment-independent config, use
ManualEngine — it detects nothing and requires each
part to be set:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\ManualEngine;
use Flytachi\Winter\Thread\Payload\TempFileTransport;
Thread::bindEngine(
(new ManualEngine())
->withBinaryPath('/usr/bin/php')
->withRunnerPath(__DIR__ . '/vendor/flytachi/winter-thread/wRunner')
->withTransport(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 binaryPath() first.
Point to the packaged runner script
The child is bootstrapped by the packaged wRunner script (not wExecutor — that name is
gone). AdaptiveEngine 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\Engine\AdaptiveEngine;
Thread::bindEngine(new AdaptiveEngine(
runnerPath: __DIR__ . '/vendor/flytachi/winter-thread/wRunner',
));A custom runner bootstrap is a child-side concern independent of the engine; 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. Three equivalent ways to supply it:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
use Flytachi\Winter\Thread\Engine\ManualEngine;
// 1. AdaptiveEngine named argument
Thread::bindEngine(new AdaptiveEngine(secret: $_ENV['APP_SECRET']));
// 2. ManualEngine wither
Thread::bindEngine((new ManualEngine())->withSecurity($_ENV['APP_SECRET']));
// 3. No code at all — set the WINTER_THREAD_SECRET env var; AdaptiveEngine reads it.The secret reaches the child through the WINTER_THREAD_SECRET environment variable
(owner-only), never argv — so signing keeps working even for a ManualEngine with an explicit
secret. Details in Security & performance.
Swoole: the transport is auto-selected
You no longer need to detect Swoole yourself. AdaptiveEngine picks
TempFileTransport automatically whenever it runs under an
active Swoole runtime (inside a coroutine, or with runtime hooks enabled) — because a raw stdin
pipe is not coroutine-safe. Outside Swoole it uses PipeTransport. The old manual
Swoole-detection snippet is no longer necessary.
You can still force a specific transport when you want to:
use Flytachi\Winter\Thread\Thread;
use Flytachi\Winter\Thread\Engine\AdaptiveEngine;
use Flytachi\Winter\Thread\Payload\ShmTransport;
// Force shared-memory delivery (requires ext-shmop).
Thread::bindEngine(new AdaptiveEngine(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
engine’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::engine()->launcher();
$payload = \Opis\Closure\serialize($runnable, Thread::engine()->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 —
Engine,AdaptiveEngine,ManualEngine,LaunchSpec,ProcessHandle - Swoole & payload delivery — why the transport auto-switches
- Payload modes — Pipe / TempFile / Shm transports
- Security & performance — signing and the secret channel