darkwood/flow
Flow is a PHP 8.5+ package for building asynchronous pipelines with a functional style. Define steps with generators, pass typed data through each stage, and await execution. Includes examples and docs, with a focus on assembling code as “flows”.
Installation:
composer require darkwood/flow
Requires PHP 8.5+.
First Flow Creation:
use Flow\FlowFactory;
use Flow\Ip;
$flow = (new FlowFactory())->create(function() {
yield fn($input) => $input + 1; // Simple transformation
});
$ip = new Ip(5);
$flow($ip);
$flow->await(); // Outputs: 6
Key Classes:
FlowFactory: Creates flow instancesIp (Input Pointer): Holds current data and contextFlow: The executable flow pipelineTransform sequential data processing (e.g., API responses → parsed → enriched → stored):
$flow = (new FlowFactory())->create(function() {
yield fn($apiResponse) => json_decode($apiResponse, true);
yield fn($parsedData) => array_map('strtoupper', $parsedData);
yield fn($uppercaseData) => ['processed_at' => now(), 'data' => $uppercaseData];
});
$ip = new Ip(file_get_contents('api/endpoint'));
$flow($ip);
$flow->await(); // Returns enriched data
Define Jobs:
$flow = (new FlowFactory())->create(function() {
// Job 1: Input → Intermediate
yield fn($input) => transform($input);
// Job 2: Intermediate → Output
yield fn($intermediate) => finalize($intermediate);
});
Execute:
$ip = new Ip($initialData);
$flow($ip);
$result = $ip->data; // Final output
$flow->await(); // Trigger async execution
Driver Selection:
// Configure default driver (e.g., Amp)
FlowFactory::setDefaultDriver(new AmpDriver());
Driver-Specific Jobs:
use Flow\Jobs\AsyncJob;
$flow = (new FlowFactory())->create(function() {
yield new AsyncJob(fn() => sleep(1)); // Non-blocking sleep
});
use Flow\Jobs\YJob;
// Recursive flow (e.g., tree traversal)
$Ywrap = static function(callable $func, callable $wrapper) {
return new YJob(static fn($recurse) => $wrapper($recurse));
};
$flow = (new FlowFactory())->create(function() use ($Ywrap) {
yield $Ywrap(
fn($recurse) => fn($node) => $node->children ? array_map($recurse, $node->children) : $node->value,
fn($recurse) => fn($node) => $recurse($node)
);
});
Service Provider:
public function register()
{
$this->app->singleton(FlowFactory::class, function($app) {
return (new FlowFactory())->setDefaultDriver(new AmpDriver());
});
}
Command Example:
use Illuminate\Console\Command;
use Flow\FlowFactory;
class ProcessDataCommand extends Command
{
protected $signature = 'data:process';
protected $description = 'Process data pipeline';
public function handle(FlowFactory $flowFactory)
{
$flow = $flowFactory->create(function() {
yield fn($data) => $this->transform($data);
yield fn($transformed) => $this->store($transformed);
});
$flow(new Ip($this->getInputData()));
$flow->await();
}
}
Driver Compatibility:
composer.json constraints or use parallel driver for CPU-bound tasks.State Management:
Ip (Input Pointer) is immutable—modify data via returned objects.yield fn($ip) => new Ip($ip->data + 1); // Correct
$ip->data++; // Anti-pattern (silently fails)
Async Deadlocks:
file_get_contents) in async flows halt execution.yield new AsyncJob(fn() => Amp\async(function() {
return file_get_contents('url');
}));
Y-Combinator Stack Overflow:
Flow Tracing:
$flow = (new FlowFactory())->create(function() {
yield fn($data) => tap($data, fn($d) => logger()->debug("Processing: {$d}"));
});
Driver-Specific Logs:
AMP_LOG_LEVEL=debug).Ip Inspection:
$flow->on('step', fn($ip) => logger()->debug("Current data:", $ip->data));
Custom Drivers:
Implement Flow\DriverInterface for new async backends:
class MyDriver implements DriverInterface {
public function run(callable $job): void { /* ... */ }
public function await(): void { /* ... */ }
}
Job Decorators: Wrap jobs for cross-cutting concerns (e.g., logging, retries):
class LoggingJob implements JobInterface {
public function __construct(private JobInterface $job, private Logger $logger) {}
public function run(Ip $ip): Ip { /* Log + delegate */ }
}
Flow Middleware:
$flow = (new FlowFactory())->create(function() {
yield new MiddlewareJob(fn($next) => fn($ip) => $next($ip)->tap(fn($ip) => $this->postProcess($ip)));
});
Fiber Driver:
Parallel Driver:
Amp Driver:
Queue Integration:
use Flow\Jobs\QueueJob;
$flow = (new FlowFactory())->create(function() {
yield new QueueJob('process-data', $data); // Dispatch to queue
});
Event Dispatching:
$flow = (new FlowFactory())->create(function() {
yield fn($data) => event(new DataProcessed($data));
});
Service Container Binding:
$this->app->bind(FlowFactory::class, function($app) {
$factory = new FlowFactory();
$factory->setDefaultDriver($app->make(AmpDriver::class));
return $factory;
});
How can I help you explore Laravel packages today?