amphp/pipeline
Fiber-safe concurrent iterators and collection operators for AMPHP. Build pipelines from iterables, map/filter/merge, and consume results from multiple fibers safely using ConcurrentIterator (foreach or manual continue/getValue/getPosition). Requires PHP 8.1+.
Installation:
composer require amphp/pipeline
Requires PHP 8.1+.
First Use Case: Transform a simple array into a concurrent pipeline and process it:
use Amp\Pipeline\Pipeline;
$pipeline = Pipeline::fromIterable([1, 2, 3, 4, 5])
->map(fn($n) => $n * 2)
->filter(fn($n) => $n % 3 === 0);
foreach ($pipeline as $value) {
echo $value . "\n"; // Output: 6
}
Key Entry Points:
Pipeline::fromIterable(): Convert arrays/generators to pipelines.Pipeline::generate(): Create pipelines from closures.Queue: For async producer-consumer patterns.Pattern: Chain operations like map, filter, tap for async data flows.
$pipeline = Pipeline::fromIterable(range(1, 100))
->concurrent(10) // Process 10 items concurrently
->tap(fn() => Amp\delay(0.1)) // Simulate I/O
->map(fn($n) => $n * 2)
->filter(fn($n) => $n % 5 === 0)
->reduce(fn($carry, $n) => $carry + $n, 0);
Workflow:
concurrent(N) for parallelism (e.g., API calls, DB queries).unordered() for out-of-order results (e.g., logging).buffer(N) to control back-pressure (e.g., throttling).Pattern: Use Queue for fiber-safe async communication.
$queue = new Queue();
Amp\async(function() use ($queue) {
foreach (range(1, 5) as $n) {
$queue->push($n); // Blocks until consumed
}
$queue->complete();
});
foreach ($queue->iterate() as $value) {
echo $value . "\n"; // Consumes asynchronously
}
Integration Tips:
Amp\async for fire-and-forget producers.pushAsync() for non-blocking pushes (returns a Future).Pattern: Combine multiple pipelines with merge().
$pipeline1 = Pipeline::fromIterable([1, 2, 3]);
$pipeline2 = Pipeline::fromIterable(['a', 'b']);
$merged = Pipeline::merge($pipeline1, $pipeline2);
foreach ($merged as $value) {
echo $value . "\n"; // Output: 1, 2, 3, a, b
}
Use Case: Aggregate logs from multiple sources or batch API responses.
Pattern: Use catch() or try-catch with Queue::error().
$queue = new Queue();
Amp\async(function() use ($queue) {
try {
foreach (range(1, 3) as $n) {
$queue->push($n);
}
} catch (Exception $e) {
$queue->error($e); // Propagates to consumer
}
$queue->complete();
});
Forgetting complete():
$queue->complete() will hang consumers indefinitely.complete() or error() after pushing all values.Back-Pressure Mismanagement:
buffer(N), producers block on every push.buffer(5) to allow 5 items in flight.Concurrent Iterator State:
getPosition() may not reflect sequential order across fibers.foreach for simplicity; avoid manual continue() unless necessary.DisposedException:
DisposedException in producers to handle early termination.if ($iterator->isComplete()) {
echo "Iterator finished\n";
}
->tap(fn($value) => error_log("Processing: $value"))
Amp\delay() to simulate slow operations and test back-pressure.Custom Operators: Create reusable pipeline methods:
$pipeline->customOp(fn($pipeline) => $pipeline->map(fn($n) => $n * 2));
Integrate with Laravel:
Queue for async job processing (e.g., Laravel Queues).$queue = new Queue();
Amp\async(function() use ($queue) {
foreach (Job::chunk(10) as $job) {
$queue->push($job->handle());
}
$queue->complete();
});
Performance Tuning:
concurrent(N) based on system resources (e.g., N=5 for I/O-bound tasks).unordered() for non-sequential tasks (e.g., parallel HTTP requests).0 (blocks on every push). Set buffer(N) to optimize throughput.fromIterable(); use Queue for streaming.
```markdown
### Laravel-Specific Notes
1. **Async Job Queues**:
Replace Laravel’s `dispatch()` with `Queue` for fiber-based workers:
```php
$queue = new Queue();
Amp\async(function() use ($queue) {
foreach (Job::all() as $job) {
$queue->push($job->fire());
}
$queue->complete();
});
Event Listeners:
Use Pipeline to process events concurrently:
event(new UserRegistered($user))
->then(fn() => Pipeline::fromIterable([$user])
->concurrent(3)
->tap(fn($user) => $this->notify($user))
->tap(fn($user) => $this->log($user)));
Database Queries:
Batch queries with concurrent():
$pipeline = Pipeline::fromIterable(User::cursor())
->concurrent(20)
->map(fn($user) => $user->load('posts'))
->toArray();
How can I help you explore Laravel packages today?