darkwood/flow
Flow is a PHP 8.5+ package for asynchronous, functional pipelines. Use FlowFactory to compose yielded callables into reusable flows, pass input through Ip, and await results. It supports visual code assembly and ships with examples for flows, Y-combinator, and server/client usage.
Position: darkwood/flow is a single-vendor, pre-1.x-equivalent PHP runtime for flow-based programming — an in-process async/functional composition engine, not a durable workflow or job system. It is technically interesting and cheap to trial, but the evidence does not support production adoption in a Laravel estate today. Recommendation: time-boxed spike only, behind an anti-corruption layer, with a pre-agreed kill criterion. Treat anything beyond that as a fork-and-own decision, not a dependency decision.
Architecture fit
Ip (readonly data envelope) is pushed through a chain of single-argument callables yielded from a factory (FlowFactory::create), with a graph of typed stage-to-stage transforms (D1 → D2 → D3 → D4). Execution is driven by pluggable runtimes: AmpDriver, ReactDriver, SwooleDriver/OpenSwoole, FiberDriver, ParallelDriver, SpatieDriver, and an experimental TrueAsyncDriver for ext-async.DriverInterface (async, defer, tick, sleep, await), an event system (Event::POOL, SYNC, PULL), IpStrategy for ordering, IpPool for pooling, and AsyncHandlerInterface for batching/deferral at the sync step. That is a genuine runtime abstraction, which is the package's main architectural asset.Symfony Messenger support was explicitly removed in v1.2.6 after being added in v1.2.1–v1.2.2. Where it could fit: long-running workers (Octane/Swoole, FrankenPHP worker mode), streaming/chunked HTTP (there is an httpchunkflow.php example), and fan-out/fan-in transforms that are awkward as discrete queued jobs.Integration feasibility
composer require darkwood/flow, MIT). Everything after that is not.AsJob attribute, Symfony-friendly packaging, Symfony-versioned releases). A Laravel service provider, config surface, container bindings for stages, queue-worker integration, and Octane lifecycle hooks are our build, not theirs.src/Darkwood/, darkwood/darkwood meta-package with replace aliases, splitsh subtree splits to satellite repos) means the repo under evaluation may be a split artifact rather than the source of truth, and Composer's replace aliases create real ambiguity about which code you actually installed. Verify provenance before pinning.Technical risk
v1.x and 8.x where minor/patch track Symfony versions), v1.3.0 claims "unified Darkwood versioning" and is then followed by v8.0.12. Consumers cannot infer breaking changes from the version string, which forces exact pinning and manual upgrade review forever.Railway FBP → Flow), namespace change (RFBP\ → Flow\), removal of IP-associated context, start/stop → await, Flow::do → FlowFactory::create, added-then-removed Symfony Messenger. This is healthy for a young library and hostile for a dependent platform.$ip->data->n3 inside a closure whose parameter is $data3, and closes printf with an unbalanced )). If the flagship snippet is not CI-executed, the examples/ directory and the GitHub Pages docs should be treated as illustrative, not authoritative.await() failures surface as driver-specific exceptions, and PHP tooling (Xdebug, Blackfire, APM agents) is immature for this execution model. On-call engineers will debug by inference.Flow\FlowInterface / DriverInterface frozen for a stated window?IpPool and multi-IP pulls (PullEvent pulling multiple Ips)?Event::SYNC handler exceptions fatal to the flow or recoverable?examples/ executed in CI on every release?darkwood/flow a split from the monorepo — what guarantees that the split is not stale relative to src/Darkwood/?Stack fit
artisan commands. Budget for that adapter explicitly.Migration path
examples/flow.php and examples/server.php + client.php, publish a findings note. Phase 1: wrap behind an internal PipelineRunner interface with two implementations — Flow-backed and current queue-backed — so the pilot is reversible by config, not by revert. Phase 2: one non-critical, non-customer-facing pipeline behind a feature flag with a documented kill switch. Phase 3: expand only on a passed gate review.Ip, FlowInterface, driver classes) leak into domain code; keep the boundary at the adapter.Compatibility
^), commit composer.lock changes in isolated PRs, and add a CI job that installs the pinned set against your real PHP matrix. Given the tag-line churn, a minor/patch bump must be treated as potentially breaking until proven otherwise.ext-async/TrueAsyncDriver is required (experimental) or optional; prefer a stable driver (FiberDriver or AmpDriver) for the spike to reduce variables.darkwood/flow install resolves to the intended source (satellite vs monorepo) and that replace aliases are not silently substituting packages.Sequencing (stage gates)
Maintenance
DriverInterface/AsyncHandlerInterface churn history; treat any extension of the driver layer as high-maintenance code.Support
Scaling
Ips. There is no evidence of dead-lettering or poison-message handling; you must build it at the boundary.IpPool/IpStrategy are undocumented in the supplied materials; unbounded queues are the default failure shape. Load-testing memory under sustained multi-IP flow is mandatory before any pilot.CollectionTransport is in-process/collection-shaped; Messenger was removed). Horizontally scaling a stateful flow across pods is not a supported pattern.Failure modes to design against
$flow->await() never returns because a stage never resolves; without a timeout/watchdog this looks like a healthy idle worker.AsyncHandler at Event::SYNC stalls or kills the flow with driver-specific exception types.Ip pooling / multi-IP pulls under load.Ip; without an adapter-level carrier, incident triage across stages is guesswork.Ramp-up
How can I help you explore Laravel packages today?