projectsaturnstudios / pocketflow-php
PHP implementation of The-Pocket/PocketFlow — minimal graph workflow orchestration.
Package info
github.com/projectsaturnstudios/pocketflow-php
pkg:composer/projectsaturnstudios/pocketflow-php
Requires
- php: ^8.2|^8.3|^8.4
- react/promise: ^3.2 || ^2.0 || ^1.1
Requires (Dev)
- pestphp/pest: ^4.0
README
Minimal graph workflow library for PHP — a port of The-Pocket/PocketFlow.
- Nodes with
prep→exec→post - Flows that route on action strings (
default,continue, …) - Shared state bag mutated across hops (Flows clone nodes; put durable data in
$shared) - Async via ReactPHP Promise (
AsyncNode/AsyncFlow) - Batch helpers for list and parallel fan-out
Package: projectsaturnstudios/pocketflow-php
Namespace: ProjectSaturnStudios\PocketFlow\
Requirements
- PHP 8.4+
react/promise(required for async APIs; syncFlow/Nodedo not need an event loop)
Install
composer require projectsaturnstudios/pocketflow-php
Quick start (sync)
<?php use ProjectSaturnStudios\PocketFlow\Flow; use ProjectSaturnStudios\PocketFlow\Node; class GreetNode extends Node { public function prep(mixed &$shared): mixed { return $shared['name'] ?? 'World'; } public function exec(mixed $prep_res): mixed { return "Hello, {$prep_res}!"; } public function post(mixed &$shared, mixed $prep_res, mixed $exec_res): mixed { $shared['greeting'] = $exec_res; return 'default'; // next action } } class PrintNode extends Node { public function post(mixed &$shared, mixed $prep_res, mixed $exec_res): mixed { echo $shared['greeting'], PHP_EOL; return null; // end flow } } $greet = new GreetNode; $print = new PrintNode; $greet->next($print); $shared = ['name' => 'PocketFlow']; (new Flow($greet))->run($shared);
Conditional edges
$nodeA->on('ok')->next($nodeB); $nodeA->next($nodeC, 'retry');
Self-loop
$tick->next($tick, 'continue'); // return 'continue' to loop, or null to stop
Async
use ProjectSaturnStudios\PocketFlow\AsyncFlow; use ProjectSaturnStudios\PocketFlow\AsyncNode; use React\Promise\PromiseInterface; use function React\Promise\resolve; class FetchNode extends AsyncNode { public function exec_async(mixed $prep_res): PromiseInterface { return resolve(['ok' => true]); } public function post_async(mixed $shared, mixed $prep_res, mixed $exec_res): PromiseInterface { $shared['result'] = $exec_res; return resolve('default'); } } $shared = []; $promise = (new AsyncFlow(new FetchNode))->run_async($shared); // $promise is a React\Promise\PromiseInterface
Array $shared values are wrapped in a SharedStore for the async run so mutations persist across promise hops, then copied back into your array when the promise settles.
Nested AsyncFlow graphs work: AsyncFlow extends AsyncNode, matching the Python MRO intent.
Batch
BatchNode/BatchFlow— sequential items / param setsAsyncBatchNode/AsyncParallelBatchNode— sequential vsPromise\allAsyncBatchFlow/AsyncParallelBatchFlow— batch orchestration
Public API notes
| Method | Role |
|---|---|
Node::run(&$shared) |
Run a single node (warns if successors exist — use Flow) |
Flow::run(&$shared) |
Orchestrate from the start node |
AsyncNode::run_async(&$shared) / AsyncFlow::run_async(&$shared) |
Promise-based entry |
post / post_async return value |
Action key for the next successor (default if null/non-string) |
Retries: Node / AsyncNode honor max_retries and wait (seconds) via _exec.
Tests
composer install composer test # or: vendor/bin/pest
CI runs Pest on PHP 8.4.
Comparison with Python PocketFlow
| Feature | Python | This package |
|---|---|---|
| Graph + action routing | Yes | Yes |
| Shared mutable store | dict | array / SharedStore |
| Async | asyncio | ReactPHP Promise |
| Batch / parallel batch | Yes | Yes |
License
MIT — same spirit as the original PocketFlow.
Acknowledgments
- The-Pocket/PocketFlow — original design
- ReactPHP — promises for async nodes