Darkwood · PHP 8.5 · Laravel · Flow
Symfony Console demo · demo:pipeline:compare
The overloaded word
Same word. Different execution contracts.
Core idea
Ask: "what are the steps?" and "how does it execute?"
Laravel Pipeline
Pipes folded outer-first
Nested closures
One sync call stack
$pipeline = array_reduce(
array_reverse($pipes),
$carry,
$destination
);
return $pipeline($passable);
Laravel execution
A(B(C(destination)))flowchart TB
subgraph onion ["through A, B, C"]
A["Pipe A\noutermost"]
B["Pipe B"]
C["Pipe C"]
D["destination"]
end
A --> B --> C --> D
$next = the rest of the chain, already folded.
Why $next matters
Run logic, then $next($passable)
$response = $next($passable), then transform
Return without calling $next → chain stops
Perfect for one HTTP request · one passable · one call stack
The demo
php bin/console demo:pipeline:compare
In-memory fixture · controlled timers · not a benchmark
Common misconception
sleep() / usleep()file_get_contents()Demo uses AmpDriver + Amp\delay() — not raw Fibers alone.
Darkwood Flow
Transformation node
T1 → T2
Instruction packet
Runtime scheduler
Completion barrier · returns void
Flow execution
flowchart LR
subgraph input ["Submit"]
I1[Ip task 1]
I2[Ip task 2]
I5[Ip task 5]
end
F[FetchJob]
H[HashJob]
S[SaveJob]
C[Collector]
I1 --> F
I2 --> F
I5 --> F
F --> H --> S --> C
foreach ($tasks as $task) { $flow(new Ip($task)); }
$flow->await();
AmpDriver
sequenceDiagram
participant T1 as Task1
participant Loop as RevoltEventLoop
participant T2 as Task2
T1->>Loop: Amp delay 0.8s
T2->>Loop: Amp delay 0.3s
Note over T1,T2: timers registered
Loop->>T2: resume 0.3s
Loop->>T1: resume 0.8s
Measurements
~2.42 s
delays accumulate (Σ 2.4s)
~0.81 s
delays overlap (max 0.8s)
gantt
title Fetch phase only
dateFormat X
axisFormat %Ls
section Sync
T1 :0, 800
T2 :800, 1100
T3 :1100, 1700
T4 :1700, 1900
T5 :1900, 2400
section Flow
T1f :0, 800
T2f :0, 300
T3f :0, 600
T4f :0, 200
T5f :0, 500
What the demo proves
Ip packets traverse the same stages[FLOW][task #1][fetch] start
[FLOW][task #2][fetch] start
[FLOW][task #5][fetch] start
[FLOW][task #4][fetch] end
What it does NOT prove
Flow is not always faster
Pipeline is not poorly designed
Single OS thread throughout
Blocking I/O would still block
Comparison
$nextthen()Ipsawait() returns voidSync demo: 5 sequential onions · Flow demo: 5 concurrent packets
What Flow can learn
thenReturn()sink() · runAll() · TraceSubscriber · hrtime() delaysChoosing the model
$nextThey coexist. Different layers, different models.
Takeaway
Always ask:
How does it execute?
Resources
darkwood/content/laravel-pipeline — runnable demodemo:pipeline:sync · demo:pipeline:flow · demo:pipeline:comparedarkwood/flow — dataflow componentRe-run timings on your machine before citing exact numbers in slides.