PHP code example of particle-academy / fancy-flow-php

1. Go to this page and download the library: Download particle-academy/fancy-flow-php library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

particle-academy / fancy-flow-php example snippets


use FancyFlow\Workflow;
use FancyFlow\NodeKindRegistry;
use FancyFlow\Registry\Builtin;
use FancyFlow\Engine\FlowRunner;
use FancyFlow\Runtime\RunOptions;

// 1. Register the built-in node kinds (or your own) so import can validate.
$registry = Builtin::register(new NodeKindRegistry());

// 2. Import a WorkflowSchema exported from <FlowEditor> (JSON string or array).
$import = Workflow::import($json, registry: $registry);   // -> ImportResult { ok, graph, issues[] }

// 3. Run it. Builtin::executors() ships batteries-

$registry->get('branch')->name;                  // "@particle-academy/branch"
$registry->get('@fancy/branch')->name;           // same kind — aliases resolve
$registry->get('llm_branch')->name;              // "@particle-academy/llm_router"

NodeKind::fromArray([
    'name' => '@acme/salesforce_upsert',
    'aliases' => ['sf_upsert_v1'],   // graphs saved before the rename still open
    'category' => 'io', 'label' => 'Upsert',
]);

use FancyFlow\Nodes\Support\ExecutorDeps;

$executors = Builtin::executors(new ExecutorDeps(
    http:     $myHttpClient,    // implements Nodes\Support\HttpClient
    llm:      $myLlmClient,     // implements Nodes\Support\LlmClient
    notifier: $mySlackNotifier, // implements Nodes\Support\Notifier
    // ...memory, data, tools, vectors
));

use FancyFlow\Capabilities\{Capabilities, LlmClient, LlmRouteRequest, LlmRouteChoice};

final class MyRouter implements LlmClient
{
    public function chooseRoute(LlmRouteRequest $request): LlmRouteChoice
    {
        // $request->prompt, $request->routes, $request->ports(), …
        return new LlmRouteChoice(port: 'billing', reason: 'duplicate charge');
    }
}

Capabilities::setLlmClient(new MyRouter());   // or bind LlmClient in the container

use FancyFlow\Capabilities\{Capabilities, WorkflowResolver};

final class FileResolver implements WorkflowResolver
{
    public function resolve(string $ref): ?FlowGraph
    {
        return Workflow::import(json_decode(file_get_contents("flows/{$ref}.json"), true))->graph;
    }
}

Capabilities::setWorkflowResolver(new FileResolver());

// 1. Kind — shape + validation, shared with the editor.
$registry->register(NodeKind::fromArray([
    'name' => 'geocode', 'category' => 'io', 'label' => 'Geocode',
    'configSchema' => [['type' => 'text', 'key' => 'address', 'label' => 'Address', 'e('node-7', SpecialExecutor::class);        // by node id (highest precedence)

use FancyFlow\Laravel\Facades\FancyFlow;

$result = FancyFlow::run($schema, ['trigger-1' => ['payload' => $request->all()]]);

// A custom node with full DI (kind + executor in one call):
FancyFlow::extend('geocode', GeocodeExecutor::class, [
    'name' => 'geocode', 'category' => 'io', 'label' => 'Geocode',
]);
// …or co-locate both with the attribute + `php artisan flow:discover`:
#[FlowNode('geocode', category: 'io', label: 'Geocode')]
final class GeocodeExecutor implements NodeExecutor { /* ... */ }

$run = FancyFlow::dispatch($schema, ['trigger-1' => ['payload' => $payload]]);
$run->status;   // pending → running → completed | partial | failed | awaiting_approval | awaiting_input
$run->outputs;  // on completed or partial

$run->approve();   // routes down the `approved` branch and continues
$run->deny();      // routes down `denied`

$run->awaitingForm();
// ['nodeId' => 'form', 'title' => 'Need your input',
//  'fields' => [['key' => 'answer', 'label' => 'Your answer', 'type' => 'textarea']]]

$run->submitInput(values: ['answer' => 'ship it']);  // emitted on the node's `out`

Route::flow('/hooks/onboard', $schema);          // POST → dispatch a durable run
class Project extends Model { use HasWorkflows; } // $project->workflows()

// config/fancy-flow.php -- up to 4 of a run's nodes at once, or "unlimited" (or 0)
'queue' => ['max_concurrent' => 4],

// one run, while the host stays serial
FancyFlow::dispatch($schema, $inputs, maxConcurrent: DispatchLimit::UNLIMITED);

#[FlowNode('git_pr_open', category: 'io', label: 'Open PR', sideEffects: 'unsafe-to-replay')]
final class OpenPullRequestExecutor implements NodeExecutor { /* … */ }

// config/fancy-flow.php
'queue' => ['driver' => 'per_node', 'tries' => 1, 'node_tries' => ['api_request' => 3]],

$node = $run->nodes()->where('node_id', 'send')->first();
$node->inputs;       // resolved input-port map, safely recorded
$node->output;
$node->status;       // claimed | completed | skipped | paused | failed
$node->attempts;
$node->error;
$node->claimed_at;
$node->completed_at;

$app['config']->set('queue.default', 'sync');

use FancyFlow\Testing\QueuePump;

Queue::fake();
QueuePump::reset();            // per test -- the cursor is static

FancyFlow::dispatch($schema);

QueuePump::drain();            // run it out
QueuePump::drain(maxNodes: 2); // ...or kill the worker after two nodes

FancyFlow::dispatchCohort(
    [$enrichFlow, $archiveFlow, $notifyFlow],   // declared order IS the run order
    ['trigger' => $deal->toArray()],            // snapshotted per run at dispatch
    guard: ['name' => 'record-exists', 'args' => ['model' => Deal::class, 'id' => $deal->id]],
);

$notify->status;          // 'skipped'
$notify->skipped_reason;  // 'App\Models\Deal 41 no longer exists'

// config/fancy-flow.php
'guards' => ['still-open' => \App\Flow\Guards\DealStillOpen::class],