PHPackages                             helvetitec/flowengine - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. helvetitec/flowengine

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

helvetitec/flowengine
=====================

Statemachine bots for conversations in Laravel

v1.4.1(2mo ago)045MITPHP

Since Mar 23Pushed 2mo agoCompare

[ Source](https://github.com/Helvetitec/flowengine)[ Packagist](https://packagist.org/packages/helvetitec/flowengine)[ RSS](/packages/helvetitec-flowengine/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (10)Dependencies (4)Versions (23)Used By (0)

FlowEngine
==========

[](#flowengine)

A simple, flexible state machine engine for Laravel to handle dynamic flows like chats, onboarding, workflows, and more.

🚀 Concept
---------

[](#-concept)

FlowEngine allows you to define **state-driven flows** where each subject (e.g. a chat, user, or process) moves through states based on input.

Typical flow:

1. Receive input
2. Process current state
3. Transition to next state
4. Persist state + context
5. Stop execution
6. Resume later (via new input or cooldown)

Setup
-----

[](#setup)

1. Publish the migrations:

```
php artisan vendor:publish --tag="helvetitec.flowengine.migrations"
```

2. Run migrations:

```
php artisan migrate
```

🧱 Core Components
-----------------

[](#-core-components)

### FlowEngine

[](#flowengine-1)

The base class that handles execution:

```
abstract class FlowEngine
{
    abstract protected function doRun(mixed $input): void;

    final public function run(FlowSubject $subject, mixed $input): void;

    final protected function subject(): FlowSubject;

    final protected function cooldown(?Carbon $until): static;

    final protected function transition(string $nextState): static;

    final protected function set(string $key, mixed $value): static;

    final protected function get(string $key, mixed $default = null): mixed;

    final protected function pull(string $key, mixed $default = null, bool $persist = false): mixed;

    final protected function delete(string $key): static;

    final protected function stop(bool $persist = true): never;

    final protected function transitionAndStop(string $nextState): never;

    final protected function deactivate(): never;
}
```

### FlowSubject

[](#flowsubject)

Any model that participates in a flow must implement:

```
interface FlowSubject
{
    public function getActive(): bool;
    public function setActive(bool $active): void;

    public function getStateKey(): string;
    public function setStateKey(string $state): void;

    public function getContext(): array;
    public function setContext(?array $context): void;

    public function getCooldown(): ?Carbon;
    public function setCooldown(?Carbon $until): void;

    public function persist(): void;
}
```

FlowRun
-------

[](#flowrun)

```
class FlowRun extends Model implements FlowSubject
{
    public function subject();

    public function getActive(): bool;

    public function setActive(bool $active): void;

    public function getStateKey(): string;

    public function setStateKey(string $state): void;

    public function getContext(): array;

    public function setContext(?array $context): void;

    public function getCooldown(): ?Carbon;

    public function setCooldown(?Carbon $until): void;

    public function persist(): void;

    public function resolveFlow(): FlowEngine;

    public function runFlow(mixed $input = null, bool $force = false): void;

    public function mergeContext(array $data): static;

    public static function clear(string $flowClass, ?Carbon $clearOlderThan = null, ?string $flowType = null, ?string $flowId = null): int;
}
```

⚡ Example Implementation
------------------------

[](#-example-implementation)

### 1. FlowRuns

[](#1-flowruns)

FlowRuns allow multiple FlowEngines running at the same time. The default state for every run is always 'start'.

```
//Add to your Subject (e.g Chat)
use HasFlowRuns;
```

### 2. Flow

[](#2-flow)

```
class ChatFlow extends FlowEngine
{
    protected function doRun(mixed $input): void
    {
        $state = $this->subject()->getStateKey();

        if(!($this->subject() instanceof Chat)){
            throw new LogicException("Subject is not instance of Chat!");
        }

        match ($state) {
            'start' => $this->start(),
            'waiting' => $this->handleAnswer($input),
            default => $this->start(),
        };
    }

    private function start(): void
    {
        ChatService::send($this->subject(), "Choose 1 or 2");

        $this->transition('waiting')
             ->set('options', [1,2]) //Sets the context for the flow
             ->stop();
    }

    private function handleAnswer($input): void
    {
        $options = $this->get('options');
        if(!in_array($input, $options)){
            ChatService::send($this->subject(), "Invalid input");
            $this->stop();
            return;
        }
        ChatService::send($this->subject(), "You chose: {$input}");

        $this->transition('done')
             ->delete('options')
             ->cooldown(now()->addMinutes(5))
             ->stop();
    }
}
```

### 3. Triggering the Flow

[](#3-triggering-the-flow)

```
$chat->runFlow(ChatFlow::class, $message, $force);
//With merged context
$chat->startFlow(ChatFlow::class)->mergeContext(['some_context_to_start' => 'Hello World'])->runFlow("input");
//Update the context for all FlowRuns of the model at once.
$chat->broadcastContext(['context_for_all_runs' => true]);
//Returns the object related to the flow. In this case it would be $chat as well as it is the owner, but its powerful inside the FlowEngine as you can call subject()->getOwner().
$chat->startFlow(ChatFlow::class)->subject()->getOwner();
//Clears all flowruns older than a specific date with the selected flowclass
FlowRun::clear(ChatFlow::class, now()->subMonth());
//Clears all flowruns from a specific model
FlowRun::clear(ChatFlow::class, now()->subMonth(), Chat::class, 1);
//Clears all flowruns older than a specific date. Prefer to use the more specific ones!
FlowRun::clearAll(now()->subMonth());
```

You typically call this from:

- Controllers
- Jobs
- Event listeners
- Webhooks

🔁 Flow Lifecycle
----------------

[](#-flow-lifecycle)

```
Input → run() → doRun()
        ↓
   state logic
        ↓
 transition()
 set()
 cooldown()
        ↓
     stop()
        ↓
   persist()

```

🧩 Helper Methods
----------------

[](#-helper-methods)

### Transition state

[](#transition-state)

Handles the transition between states.

```
$this->transition('next_state');
```

### Store data in context

[](#store-data-in-context)

Stores and loads data from the context.

```
$this->set('key', 'value');

$value = $this->get('key');
```

### Pull data from context and delete

[](#pull-data-from-context-and-delete)

```
$value = $this->pull('key');
```

### Delete data from context

[](#delete-data-from-context)

```
$this->delete('key');
```

### Clear all data from context

[](#clear-all-data-from-context)

```
$this->clear();
```

### Cooldown

[](#cooldown)

Adds a cooldown between this run and the next run.

```
$this->cooldown(now()->addMinutes(10));
```

### Stop execution

[](#stop-execution)

Stops the execution of the current flow.

```
$this->stop(); //persists
```

or

```
$this->stop(persist: false); //does not persist
```

### Transition and Stop

[](#transition-and-stop)

Sets the next state and stops.

```
$this->transitionAndStop('next_state');
```

### Deactivate

[](#deactivate)

Deactivates the flow and stops it.

```
$this->deactivate();
```

### Pause flow

[](#pause-flow)

```
$this->pause(now()->addMinutes(5));
```

### Reset Flow

[](#reset-flow)

```
$this->reset(now()->addHour());
```

⚠️ Important Rules
------------------

[](#️-important-rules)

### 1. Always use `run()`

[](#1-always-use-run)

```
// ✅ Correct
app(MyFlow::class)->run($subject, $input);

// ❌ Wrong
$flow->doRun($input);
```

### 2. Always stop after sending output

[](#2-always-stop-after-sending-output)

```
$this->transition('next')
     ->stop();
```

### 3. Do not persist manually

[](#3-do-not-persist-manually)

Persistence is handled automatically by the engine.

### 4. Use state keys as strings

[](#4-use-state-keys-as-strings)

```
'start'
'waiting_for_input'
'completed'
```

5. Hints
--------

[](#5-hints)

### Exceptions

[](#exceptions)

The FlowEngine will throw a FlowEngineException if you call FlowEngine-&gt;run(). This exception has the following additional context for easier debugging: **flow\_engine\_class**Returns the class of the FlowEngine like ChatFlowExample.

**flow\_engine\_context**Returns the current context of the FlowSubject.

**flow\_engine\_state**Returns the current state of the FlowSubject.

**input**Returns the latest input of the run.

### Enable/Disable flows

[](#enabledisable-flows)

You can fully disable flows by setting the setActive/getActive methods to a custom field or use setActive in FlowRuns.

```
protected $fillable = [
    'flow_active'
];

protected $casts = [
    'flow_active' => 'boolean'
];

public function setActive(bool $active): void
{
    $this->flow_active = $active;
}

public function getActive(): bool
{
    return $this->flow_active;
}
```

6. 📄 AI Usage
-------------

[](#6--ai-usage)

AI was used to create this readme file and for smaller parts of the code to make it cleaner.

###  Health Score

37

—

LowBetter than 81% of packages

Maintenance83

Actively maintained with recent releases

Popularity9

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

22

Last Release

88d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/60240491?v=4)[Roman Wanner](/maintainers/daredloco)[@daredloco](https://github.com/daredloco)

---

Top Contributors

[![daredloco](https://avatars.githubusercontent.com/u/60240491?v=4)](https://github.com/daredloco "daredloco (56 commits)")

###  Code Quality

Static AnalysisPHPStan

### Embed Badge

![Health badge](/badges/helvetitec-flowengine/health.svg)

```
[![Health](https://phpackages.com/badges/helvetitec-flowengine/health.svg)](https://phpackages.com/packages/helvetitec-flowengine)
```

###  Alternatives

[netgen/tagsbundle

Netgen Tags Bundle is an Ibexa DXP bundle for taxonomy management and easier classification of content, providing more functionality for tagging content than ibexa\_keyword field type included in Ibexa core.

48464.0k27](/packages/netgen-tagsbundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
