wundii/flowcrafter
最新稳定版本:0.23.0
Composer 安装命令:
composer require wundii/flowcrafter
包简介
PHP library for defining, executing, and monitoring message-driven workflows (state machines)
README 文档
README
PHP-Engine für message-driven Workflows — Schema-as-Code, typsicheres Routing über Message-Klassen, synchrone und asynchrone Ausführung mit vollständigem Audit-Log.
Features
- Typsichere Workflow-Definitionen als PHP-Klassen — kein YAML/XML
- Storage-Backends: MySQL, Redis, EventSourcingDB mit
SQLite-Service-Layer als Query-Cache — eigene Backends via
StorageInterfacefrei erweiterbar - Synchrone Ausführung (
FlowRunner) + asynchrone Queue-Verarbeitung (FlowObserver) + zeitgesteuerte Ausführung (FlowScheduler) - Automatischer Flow-Status, vollständiges Message-, Exception- & Schedule-Exception-Logging, Schema-Versionierung via Hash
#[FlowGroup]- und#[FlowSchedule(group:)]-Attribute für UI-Gruppierung von Flow-Typen und Schedules- UI-DevTool wird automatisch aktiviert wenn der Server via
bin/flowcrafter devgestartet wird - REST-API für Flows, Schemas, Queues, Exceptions & Schedule-Exceptions inkl. Prometheus/OpenMetrics-Endpunkt;
- Symfony Console Commands für Config, Storage-Init/Rebuild, Dev-Server, Observer, Scheduler und Mermaid-Diagramme
- Testing-Helper (
FlowTestCase,FlowAssertTrait) für storageless Unit-Tests
Installation
composer require wundii/flowcrafter
Dokumentation
Die vollständige Dokumentation liegt im docs/-Ordner:
| Kapitel | Inhalt |
|---|---|
| Console Commands | Command-Referenz |
| Deployment | Produktion: FrankenPHP + Docker |
| Entwicklung | QA-Scripts für Contributor |
| Getting Started | Erste Schritte: Config, Storage, Dev-Server |
| Konfiguration | flowcrafter.php, Storage-Backends, Server-Einstellungen |
| Konzepte | Flow, Status, Schema, Messages, includeSteps, Observer |
| Monitoring | Prometheus / OpenMetrics, CheckMK |
| REST-API | Endpunkte, Pagination, Auth |
| Testing | Flows & Steps testen mit PHPUnit 11+ |
Quickstart
# 1. Config-Datei erzeugen vendor/bin/flowcrafter config:create # 2. Storage initialisieren vendor/bin/flowcrafter storage:init # 3. Dev-Server (API + Observer + Scheduler) starten vendor/bin/flowcrafter dev
Details siehe docs/getting-started.md.
Web-UI
Das optionale Web-Frontend FlowCrafter UI visualisiert Flows, Messages, Exceptions, Schedules und Queues in Echtzeit:
docker run -p 5173:5173 -v ./data:/flowcrafter/data wundii/flowcrafter-ui:latest
Claude Code Plugin
Das optionale Claude Code Plugin flowcrafter-claude erweitert Claude Code mit Flowcrafter-Wissen — Flows, Steps, Messages und Schedules lassen sich per Slash-Command generieren und analysieren, ohne das Framework-Modell im Kopf behalten zu müssen.
/plugin marketplace add wundii/flowcrafter-claude
/plugin install flowcrafter@flowcrafter-claude
| Command | Beschreibung |
|---|---|
/create-flow |
Flow-Klasse mit FlowBuilder-DSL generieren |
/create-step |
Step-Klasse mit Message-Injection generieren |
/create-message |
Message-Klasse (init / data / return) generieren |
/create-schedule |
Schedule-Klasse mit Cron-Ausdruck generieren |
/analyze-flow |
Flow auf Fehler und Verbesserungen prüfen |
Der flowcrafter-Skill wird zusätzlich automatisch aktiviert, sobald
Flowcrafter-Begriffe im Gespräch auftauchen — ohne manuellen Befehl.
Minimalbeispiel
Messages
readonly Value-Objects. Drei Typen: Init startet den Flow, Data fließt zwischen Steps, Return beendet den Flow:
use Wundii\Flowcrafter\AbstractMessage; use Wundii\Flowcrafter\Interface\MessageDataInterface; use Wundii\Flowcrafter\Interface\MessageInitInterface; use Wundii\Flowcrafter\Interface\MessageReturnInterface; readonly class OrderInit extends AbstractMessage implements MessageInitInterface { public function __construct(private string $sku) {} public function getSku(): string { return $this->sku; } } readonly class OrderValidated extends AbstractMessage implements MessageDataInterface { public function __construct(private string $sku, private int $quantity) {} public function getSku(): string { return $this->sku; } public function getQuantity(): int { return $this->quantity; } } readonly class OrderCompleted extends AbstractMessage implements MessageReturnInterface { public function __construct(private string $summary) {} public function getSummary(): string { return $this->summary; } }
Braucht der erste Step keinen externen Input, kann statt einer eigenen Init-Klasse die mitgelieferte
Wundii\Flowcrafter\EmptyInitMessage verwendet werden. Damit Rector den Konstruktor-Parameter nicht als
ungenutzt entfernt, wird sie als public readonly promoted Property deklariert:
use Wundii\Flowcrafter\EmptyInitMessage; class StartStep implements StepInterface { public function __construct( public readonly EmptyInitMessage $init, ) {} /** @return class-string[] */ public function returnTypes(): array { return [OrderValidated::class]; } public function process(): MessageDataInterface { return new OrderValidated('SKU-1', quantity: 1); } }
Steps
reine PHP-Klassen. Der Constructor-Typ entscheidet das Routing. Ein Step kann MessageData (→ Flow läuft weiter), MessageReturn
(→ Flow endet) oder bool (→ Leaf-Result) zurückgeben:
use Wundii\Flowcrafter\Interface\MessageDataInterface; use Wundii\Flowcrafter\Interface\MessageReturnInterface; use Wundii\Flowcrafter\Interface\StepInterface; // Zwischenschritt: Init → Data class ValidateStep implements StepInterface { public function __construct(private readonly OrderInit $init) {} /** @return class-string[] */ public function returnTypes(): array { return [OrderValidated::class]; } public function process(): MessageDataInterface { return new OrderValidated($this->init->getSku(), quantity: 1); } } // Haupt-Branch: Data → Return (beendet den Flow) class CompleteOrderStep implements StepInterface { public function __construct(private readonly OrderValidated $validated) {} /** @return class-string[] */ public function returnTypes(): array { return [OrderCompleted::class]; } public function process(): MessageReturnInterface { return new OrderCompleted(sprintf( 'Order %s x%d completed', $this->validated->getSku(), $this->validated->getQuantity(), )); } } // Leaf-Step: Data → bool (FlowResult, kein Weiterleiten) class AuditStep implements StepInterface { public function __construct(private readonly OrderValidated $validated) {} /** @return class-string[] */ public function returnTypes(): array { return []; } public function process(): bool { return $this->validated->getQuantity() > 0; } }
Flow
Schema via FlowBuilder, kein YAML. Zwei Steps konsumieren OrderValidated parallel.
Optional kann ein Flow mit #[FlowGroup] einer UI-Gruppe zugeordnet werden — beeinflusst den Schema-Hash nicht:
use Wundii\Flowcrafter\Attribute\FlowGroup; #[FlowGroup('Order Management')] class OrderFlow implements FlowInterface { ... }
use Wundii\Flowcrafter\FlowBuilder; use Wundii\Flowcrafter\FlowSchema; use Wundii\Flowcrafter\Interface\FlowInterface; class OrderFlow implements FlowInterface { public static function schema(): FlowSchema { $builder = new FlowBuilder('flow.order.v1', OrderInit::class, OrderCompleted::class); $builder->addStep(ValidateStep::class); $builder->addStep(CompleteOrderStep::class); $builder->addStep(AuditStep::class); return $builder->build(); } }
Flow-Diagramm
automatisch aus dem Schema generierbar via vendor/bin/flowcrafter diagram:mermaid App\\OrderFlow:
--- title: flow.order.v1 theme: neo --- stateDiagram-v2 [*]-->ValidateStep: OrderInit ValidateStep-->CompleteOrderStep: OrderValidated ValidateStep-->AuditStep: OrderValidated CompleteOrderStep-->[*]: OrderCompletedLoading
Flow auslösen
Zwei Wege: synchron im eigenen Code via FlowRunner oder asynchron über die Queue (vom FlowObserver abgearbeitet).
Synchron — direkter Aufruf, Ergebnis sofort verfügbar:
use Wundii\Flowcrafter\FlowRunner; $flowRunner = new FlowRunner( type: 'flow.order.v1', flowSource: OrderFlow::class, flowSubject: 'sku-42', // optional, Geschäfts-Key zur späteren Suche storage: $storage, // aus $flowcrafterConfig->getStorage() ); $result = $flowRunner->run(new OrderInit('sku-42')); // $result ist MessageReturnInterface|bool — hier: OrderCompleted
Asynchron — Message in die Queue legen, der FlowObserver-Worker führt sie aus:
$storage->appendObserveItem( type: 'flow.order.v1', flowSource: OrderFlow::class, flowHash: null, // null = neuer Flow, sonst Re-Run einer bestehenden Instanz messageSource: OrderInit::class, message: (new OrderInit('sku-42'))->jsonSerialize(), flowSubject: 'sku-42', );
Alternativ über die REST-API: POST /api/flow/flow-run (synchron) bzw. POST /api/queue/enqueue (async) — siehe docs/api.md.
Zeitgesteuert — Schedule-Klasse mit Cron-Ausdruck, wird automatisch vom FlowScheduler entdeckt und ausgeführt:
use Wundii\Flowcrafter\Attribute\FlowSchedule; use Wundii\Flowcrafter\Schedule\AbstractSchedule; #[FlowSchedule('0 */6 * * *', name: 'order-cleanup', group: 'Maintenance')] class OrderCleanupSchedule extends AbstractSchedule { public function process(): void { $this->enqueue(OrderFlow::class, new OrderInit('scheduled-cleanup')); // oder synchron: $this->run(OrderFlow::class, new OrderInit('cleanup')); } }
Schedule-Klassen werden über das #[FlowSchedule]-Attribut automatisch aus dem Composer-Classmap entdeckt — keine manuelle Registrierung nötig. Der Scheduler läuft als eigenständiger Prozess (vendor/bin/flowcrafter scheduler) oder im Dev-Modus inline mit.
Dependency Injection
Steps können neben Messages auch externe Services per Constructor-Injection erhalten. Die Abhängigkeiten werden über dependenciesInjection in FlowRunner, FlowScheduler und FlowAssertTrait registriert — drei Modi stehen zur Verfügung:
| Schlüssel | Wert | Verhalten |
|---|---|---|
| ohne Schlüssel | class-string |
Klasse wird automatisch per Autowiring registriert |
| ohne Schlüssel | object |
Konkrete Instanz, gebunden an die eigene Klasse |
| Interface-Klassenname | object |
Instanz wird an Interface und Konkreten Klasse gebunden (Alias) |
// Step mit Interface-Abhängigkeit class FetchStep implements StepInterface { public function __construct( private readonly OrderInit $init, private readonly HttpClientInterface $http, // Interface, kein Concrete! ) {} public function returnTypes(): array { return [OrderValidated::class]; } public function process(): MessageDataInterface { $data = $this->http->get('/api/order/' . $this->init->getSku()); return new OrderValidated($this->init->getSku(), $data['quantity']); } }
// FlowRunner mit Interface-Binding $flowRunner = new FlowRunner( type: 'flow.order.v1', flowSource: OrderFlow::class, storage: $storage, dependenciesInjection: [ // Interface → konkrete Instanz HttpClientInterface::class => new CurlHttpClient(), // oder: direkte Instanz ohne Interface new MyLogger(), // oder: Klasse per Autowiring SomeService::class, ], ); $result = $flowRunner->run(new OrderInit('sku-42'));
// Test mit Interface-Binding in FlowAssertTrait $this->setDependenciesInjection([ HttpClientInterface::class => new CurlHttpClientMock(), ]); $this->runFlow('flow.order.v1', OrderFlow::class, new OrderInit('sku-42'));
Test
storageless mit FlowTestCase, kein Docker nötig:
use Wundii\Flowcrafter\Testing\FlowTestCase; final class OrderFlowTest extends FlowTestCase { public function testHappyPath(): void { $this->runFlow( flowType: 'flow.order.v1', flowSource: OrderFlow::class, initMessage: new OrderInit('sku-42'), ); $this->assertFlowOk(); $this->assertStepExecuted(ValidateStep::class); $this->assertStepExecuted(CompleteOrderStep::class); $this->assertStepExecuted(AuditStep::class); $this->assertFlowHasMessage(OrderValidated::class); $this->assertFlowBoolResult(true); // AuditStep lieferte true $return = $this->assertFlowReturned(OrderCompleted::class); $this->assertSame('Order sku-42 x1 completed', $return->getSummary()); } }
Vollständiger Testing-Leitfaden: docs/testing.md.
Lizenz
MIT — siehe LICENCE.
统计信息
- 总下载量: 59
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 7
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-03-10