PHPackages                             perfocard/flow - 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. [Database &amp; ORM](/categories/database)
4. /
5. perfocard/flow

ActiveLibrary[Database &amp; ORM](/categories/database)

perfocard/flow
==============

Package for Laravel that adds status flow management for Eloquent models with events and listeners (QUEUED, PROCESSING, ERROR, COMPLETE).

v1.2.3(2mo ago)0285↓33.3%MITPHPPHP ^8.2

Since Aug 29Pushed 2mo agoCompare

[ Source](https://github.com/perfocard/flow)[ Packagist](https://packagist.org/packages/perfocard/flow)[ RSS](/packages/perfocard-flow/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (4)Versions (16)Used By (0)

Perfocard Flow
==============

[](#perfocard-flow)

[![Packagist Version](https://camo.githubusercontent.com/2a891c6dd90a3fec82c39f9235acb39794549a1a7505bcccbd64a3e2ec5c01b1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f706572666f636172642f666c6f772e737667)](https://packagist.org/packages/perfocard/flow) [![Packagist Downloads](https://camo.githubusercontent.com/7cb187dc198af30a10de4d565e5b108e4d99861cb2fdf5fc50f27497ee935c90/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f706572666f636172642f666c6f772e737667)](https://packagist.org/packages/perfocard/flow) [![License](https://camo.githubusercontent.com/52d51c35a79cb143f0fe6fcab82053174ef16ce4c0967257b563b8c42fd2e7a0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f706572666f636172642f666c6f772e737667)](https://packagist.org/packages/perfocard/flow) [![PHP Version](https://camo.githubusercontent.com/f274759dfa3c358f1be549f6dccad9ba5a738347f638757814c9f097a2019385/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f706572666f636172642f666c6f772e737667)](https://packagist.org/packages/perfocard/flow)

Perfocard Flow is a lightweight, extensible package for managing the statuses of business objects and archiving large payloads in Laravel projects.

Overview:

- Centralized enum-based status model and lifecycle management for business objects.
- Automatic archiving of large payloads to remote storage (ZIP) to reduce database size, with restore and purge capabilities.
- Integration with Laravel Nova: built-in resources, a status field, actions (Compress / Restore / Purge / Defibrillate) and universal metrics.
- Support for asynchronous processing via Tasks, Endpoints and queued listeners/jobs; includes stubs for quick setup.
- Installer that publishes configuration and stubs, plus scheduler commands (`flow:compress`, `flow:purge`).

Benefits:

- Clean architecture for handling long-running and failed processes (defibrillation + events).
- Reduced database size through payload archiving.
- Ready-made Nova integration to speed up administrative workflows.
- Task/Endpoint structure that simplifies testing and scaling.

Use cases:

- When object statuses require complex business flows.
- When payloads grow large and bloat the database.
- When interactive administration via Nova is needed.

Contents
--------

[](#contents)

- [Perfocard Flow](#perfocard-flow)
    - [Contents](#contents)
    - [1. Installation](#1-installation)
        - [Package installation](#package-installation)
        - [Run the package installer (publishes config and stubs)](#run-the-package-installer-publishes-config-and-stubs)
        - [Migrations](#migrations)
    - [2. Configuration](#2-configuration)
        - [Base Nova Resource](#base-nova-resource)
        - [Registering the Nova resource](#registering-the-nova-resource)
        - [Disks for compression](#disks-for-compression)
        - [Scheduler](#scheduler)
    - [3. Usage](#3-usage)
        - [3.1 Generating a model and migration](#31-generating-a-model-and-migration)
            - [Example migration](#example-migration)
        - [3.2 The Document model](#32-the-document-model)
        - [3.3 Generating a status](#33-generating-a-status)
        - [3.4 Example implementation of DocumentStatus](#34-example-implementation-of-documentstatus)
        - [3.5 Adding casts to the model](#35-adding-casts-to-the-model)
        - [3.6 Generating an event](#36-generating-an-event)
        - [3.7 Creating a listener](#37-creating-a-listener)
        - [3.8 Using a Task](#38-using-a-task)
        - [3.9 Using an Endpoint](#39-using-an-endpoint)
            - [Example of using an Endpoint in a listener](#example-of-using-an-endpoint-in-a-listener)
    - [4. Laravel Nova integration](#4-laravel-nova-integration)
        - [4.1 Status resource](#41-status-resource)
        - [4.2 Status field](#42-status-field)
        - [4.3 Actions](#43-actions)
            - [Defibrillation](#defibrillation)
            - [Programmatic defibrillation](#programmatic-defibrillation)
            - [Archiving payloads](#archiving-payloads)
            - [Restoring payloads](#restoring-payloads)
            - [Purging payloads](#purging-payloads)
        - [4.4 Metrics](#44-metrics)
            - [NewItems](#newitems)
            - [ItemsPerDay](#itemsperday)
            - [ItemsByEnum](#itemsbyenum)

1. Installation
---------------

[](#1-installation)

### Package installation

[](#package-installation)

```
composer require perfocard/flow
```

### Run the package installer (publishes config and stubs)

[](#run-the-package-installer-publishes-config-and-stubs)

⚠️ Note: the installer publishes configuration and stub files into your application. If you've previously published any of these stubs, the installer will not overwrite them. Rename or remove the existing stub files in your project before running the installer to allow the package to publish its versions.

The package publishes the following stubs:

- `endpoint.stub`
- `enum.stub`
- `listener.queued.stub`
- `listener.typed.queued.stub`
- `migration.create.stub`
- `model.stub`
- `status.stub`
- `task.stub`
- `nova/resource.stub`

```
php artisan flow:install
```

### Migrations

[](#migrations)

```
php artisan migrate
```

2. Configuration
----------------

[](#2-configuration)

### Base Nova Resource

[](#base-nova-resource)

In your application make `App\Nova\Resource` extend the package resource instead of the default Nova resource. In `app/Nova/Resource.php` add or change the import and the base class:

```
use Perfocard\Flow\Nova\Resource as FlowNovaResource;

abstract class Resource extends FlowNovaResource
{
    // ...
}
```

### Registering the Nova resource

[](#registering-the-nova-resource)

Add the `Status` resource in `NovaServiceProvider::resources()` so it appears in Nova (this method is absent by default, so you will most likely need to add it manually):

```
class NovaServiceProvider extends NovaApplicationServiceProvider
{
    // ... other methods

    public function resources()
    {
        parent::resources();

        Nova::resources([
            \Perfocard\Flow\Nova\Resources\Status::class,
        ]);
    }
}
```

### Disks for compression

[](#disks-for-compression)

In `config/flow.php` specify the disks used for compression:

- `compression.disk.remote` — the disk for storing finished ZIP archives (e.g. `s3`).
- `compression.disk.temp` — a temporary disk for creating archives (e.g. `local`). You can define a separate disk for temporary files if needed.

⚠️ Make sure these disks are declared in `config/filesystems.php` and have read/write permissions. Do not use the same disk for both `remote` and `temp`.

**Example .env variables**

```
FLOW_COMPRESSION_DISK_REMOTE=s3
FLOW_COMPRESSION_DISK_TEMP=local
FLOW_COMPRESSION_TIMEOUT=2880
FLOW_PURGE_TIMEOUT=2880
```

### Scheduler

[](#scheduler)

Add scheduled tasks to automatically run the package commands. This is needed to save database space: payloads from the statuses table will be moved to the remote filesystem and archived there automatically.

```
// routes/console.php

use Illuminate\Support\Facades\Schedule;

Schedule::command('flow:compress')->everyMinute();
Schedule::command('flow:purge')->everyMinute();
```

Remember: in production you must have cron that runs `php artisan schedule:run` every minute.

3. Usage
--------

[](#3-usage)

All examples below use a fictional `Document` model to demonstrate practical scenarios and how the package works.

### 3.1 Generating a model and migration

[](#31-generating-a-model-and-migration)

In Laravel you can generate a model and migration with the standard command:

```
php artisan make:model Document -m
```

This command will create the model class `app/Models/Document.php` and a migration file in `database/migrations`.

#### Example migration

[](#example-migration)

```
return new class extends Migration
{
    public function up(): void
    {
        Schema::create('documents', function (Blueprint $table) {
            $table->id();
            // Core field for the Perfocard Flow package
            $table->unsignedTinyInteger('status');

            // Other fields
            $table->string('title');
            $table->text('content')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('documents');
    }
};
```

### 3.2 The Document model

[](#32-the-document-model)

When you generate the model using the published package stub, the generated model already extends the package base model `Perfocard\\Flow\\Models\\FlowModel`. No manual change is required.

```
// app/Models/Document.php

use Perfocard\Flow\Models\FlowModel;

class Document extends FlowModel
{
    protected $fillable = [
        'status',
        'title',
        'content',
    ];
}
```

> Using `FlowModel` enables the package to manage statuses and automatically integrate the business process into your model.

### 3.3 Generating a status

[](#33-generating-a-status)

For the `Document` model you need to create a corresponding status enum class. Generate it using the command:

```
php artisan make:status DocumentStatus
```

This will generate `app/Models/DocumentStatus.php`, where you will describe all possible statuses and their transitions. The file will be located next to the `Document` model.

### 3.4 Example implementation of DocumentStatus

[](#34-example-implementation-of-documentstatus)

```
// app/Models/DocumentStatus.php

use Perfocard\Flow\Contracts\ShouldBeDefibrillated;
use Perfocard\Flow\Contracts\ShouldDispatchEvents;
use Perfocard\Flow\Contracts\BackedEnum;
use Perfocard\Flow\Traits\IsBackedEnum;

enum DocumentStatus: int implements BackedEnum, ShouldBeDefibrillated, ShouldDispatchEvents
{
    use IsBackedEnum;

    case QUEUED = 0;
    case PROCESSING = 1;
    case ERROR = 2;
    case COMPLETE = 3;

    // ... other methods, not necessary for this example
}
```

### 3.5 Adding casts to the model

[](#35-adding-casts-to-the-model)

After generating the status enum you should add the attributes and the `casts()` method to the `Document` model. This sets a default status and casts the attribute to the enum class:

```
// app/Models/Document.php

class Document extends FlowModel
{
    // ... other properties and methods

    protected $attributes = [
        'status' => DocumentStatus::QUEUED,
    ];

    protected function casts(): array
    {
        return [
            'status' => DocumentStatus::class,
        ];
    }
}
```

### 3.6 Generating an event

[](#36-generating-an-event)

You need an event to be dispatched when a document is queued. Use:

```
php artisan make:event DocumentQueued
```

This will create `app/Events/DocumentQueued.php`. Add the `Document` model to the generated event constructor:

```
// app/Events/DocumentQueued.php

use App\Models\Document;

class DocumentQueued
{
    public function __construct(
        public Document $document,
    ) {}
}
```

Also register this event in the `DocumentStatus` class in the `events()` method. Example:

```
// app/Models/DocumentStatus.php

use App\Events\DocumentQueued;

enum DocumentStatus: int
{
    // ... other methods and properties

    public function events(): array
    {
        return match ($this) {
            self::QUEUED => [
                DocumentQueued::class,
            ],
            default => [],
        };
    }
}
```

### 3.7 Creating a listener

[](#37-creating-a-listener)

When a document is created it will dispatch the `DocumentQueued` event. You need a listener to handle this event.

Run:

```
php artisan make:listener Document/ProcessQueuedDocument --event=DocumentQueued --queued
```

This will generate `app/Listeners/Document/ProcessQueuedDocument.php`. The listener will listen for the event and run in the queue. Example implementation:

```
// app/Listeners/Document/ProcessQueuedDocument.php

use App\Models\DocumentStatus;

class ProcessQueuedDocument
{
    public function handle(DocumentQueued $event): void
    {
        $event->document->setStatusAndSave(
            status: DocumentStatus::PROCESSING,
        );

        $event->document->content = 'Lorem ipsum dolor sit amet.';

        $event->document->setStatusAndSave(
            status: DocumentStatus::COMPLETE,
        );
    }

    /**
     * Handle a job failure.
     */
    public function failed(DocumentQueued $event, Throwable $exception): void
    {
        parent::saveException(
            resource: $event->document,
            status: DocumentStatus::ERROR,
            exception: $exception,
        );

        throw $exception;
    }
}
```

### 3.8 Using a Task

[](#38-using-a-task)

An alternative approach is to move processing logic to a separate Task class.

Generate the class:

```
php artisan make:task DocumentGenerator -m Document
```

This will create `app/Tasks/DocumentGenerator.php`.

Example implementation:

```
// app/Tasks/DocumentGenerator.php

use App\Models\DocumentStatus;

class DocumentGenerator implements HandledTask
{
    public function processing(FlowModel $model): BackedEnum
    {
        return DocumentStatus::PROCESSING;
    }

    public function complete(FlowModel $model): BackedEnum
    {
        return DocumentStatus::COMPLETE;
    }

    public function handle(FlowModel $model): FlowModel
    {
        $model->content = 'Lorem ipsum dolor sit amet.';
        return $model;
    }
}
```

After creating `DocumentGenerator` you can use it in the listener instead of inline logic. Example:

```
// app/Listeners/Document/ProcessQueuedDocument.php

use Perfocard\Flow\Task;
use App\Tasks\DocumentGenerator;

class ProcessQueuedDocument
{
    public function handle(DocumentQueued $event): void
    {
        Task::for(DocumentGenerator::class)
            ->on($event->document)
            ->dispatch();
    }

    public function failed(DocumentQueued $event, Throwable $exception): void
    {
        parent::saveException(
            resource: $event->document,
            status: DocumentStatus::ERROR,
            exception: $exception,
        );

        throw $exception;
    }
}
```

In this case, the status will be automatically set to `PROCESSING` before the task starts and to `COMPLETE` after it is finishes.

### 3.9 Using an Endpoint

[](#39-using-an-endpoint)

Another implementation option is to use an Endpoint when document content must be fetched from an external HTTP service. Generate an endpoint class:

```
php artisan make:endpoint ExternalDocumentContent -m Document
```

This will create `app/Endpoints/ExternalDocumentContent.php`.

Example implementation:

```
// app/Endpoints/ExternalDocumentContent.php

use App\Models\DocumentStatus;

class ExternalDocumentContent
{
    public function processing(): BackedEnum
    {
        return DocumentStatus::PROCESSING;
    }

    public function complete(): BackedEnum
    {
        return DocumentStatus::COMPLETE;
    }

    public function method(FlowModel $model): string
    {
        return 'POST';
    }

    public function url(FlowModel $model): string
    {
        return 'https://example.com/api/generate';
    }

    public function buildPayload(FlowModel $model): array
    {
        return [
            'title' => $model->title,
        ];
    }

    public function processResponse(Response $response, FlowModel $model): FlowModel
    {
        $model->content = $response->json('content');
        return $model;
    }
}
```

#### Example of using an Endpoint in a listener

[](#example-of-using-an-endpoint-in-a-listener)

After creating `ExternalDocumentContent` you can use it in a listener instead of inline logic. Example:

```
// app/Listeners/Document/ProcessQueuedDocument.php

use Perfocard\Flow\Endpoint;
use App\Endpoints\ExternalDocumentContent;

class ProcessQueuedDocument extends ThrowableListener
{
    public function handle(DocumentQueued $event): void
    {
        Endpoint::for(ExternalDocumentContent::class)
            ->on($event->document)
            ->dispatch();
    }

    public function failed(DocumentQueued $event, Throwable $exception): void
    {
        parent::saveException(
            resource: $event->document,
            status: DocumentStatus::ERROR,
            exception: $exception,
        );

        throw $exception;
    }
}
```

In this case, the status will automatically be set to `PROCESSING` before the call and to `COMPLETE` after it finishes. Additionally, the request payload and the response are automatically saved to the `payload` field of the corresponding status record.

4. Laravel Nova integration
---------------------------

[](#4-laravel-nova-integration)

This package ships with built-in support for Laravel Nova and provides resources, fields, filters and some universal metrics out of the box.

### 4.1 Status resource

[](#41-status-resource)

All statuses for different models are stored in a separate `statuses` table. The package includes a model and a Nova resource `Status` and supports polymorphic relations with your models.

If you need to view all statuses in Laravel Nova, enable it in the configuration:

```
// config/flow.php

return [
    'status' => [
        // ... other settings

        'nova_navigation' => true,
    ],
];
```

After that the Status resource will appear in your Nova panel.

### 4.2 Status field

[](#42-status-field)

When installing the package a stub for the Nova resource is published or replaced. As a result, newly generated Nova resources include modified methods.

When generating a resource run:

```
php artisan nova:resource Document
```

Note the fields method — instead of directly returning an array it calls the parent `mergeFields` method:

```
// app/Nova/Document.php

class Document extends Resource
{
    public function fields(NovaRequest $request)
    {
        return parent::mergeFields([
            //
        ]);
    }
}
```

This allows the status field to be automatically displayed in Nova without manual definition. This is the default behavior and should work for most cases. In special cases you can return a regular array to revert to Nova's default behavior.

### 4.3 Actions

[](#43-actions)

The package provides several Nova actions out of the box.

#### Defibrillation

[](#defibrillation)

This feature restarts the business process. To use it, implement the `defibrillation` method in your status enum and ensure it implements the `ShouldBeDefibrillated` contract:

```
// app/Models/DocumentStatus.php

use Perfocard\Flow\Contracts\ShouldBeDefibrillated;

enum DocumentStatus: int implements ShouldBeDefibrillated
{
    public function defibrillate(): ?self
    {
        return match ($this) {
            self::ERROR => self::QUEUED,
            default => null,
        };
    }
}
```

Now, if a resource has the `ERROR` status, Nova will show the `Defibrillate` action on that resource and executing it will change the status from `ERROR` to `QUEUED` and the business process will be restarted.

#### Programmatic defibrillation

[](#programmatic-defibrillation)

You can also defibrillate programmatically:

```
use App\Models\Document;
use App\Models\DocumentStatus;

$document = Document::where('status', DocumentStatus::ERROR)->first();

// defibrillate
$document->defibrillate();
```

#### Archiving payloads

[](#archiving-payloads)

Statuses contain a `payload` field which can grow large over time (for example when using an Endpoint, both the request and response can be stored). To avoid large database growth use the archiving feature.

If you configure the scheduler to run `php artisan flow:compress` regularly this process will run automatically.

If you need to archive payloads manually (for example before the scheduled compress run) use the `Compress` action in Nova. This will archive the payload immediately. The action is available on a specific status resource and only when the payload has not yet been archived.

#### Restoring payloads

[](#restoring-payloads)

Once payloads are archived they are no longer present in the database field. To restore them for analysis use the `Restore` action. This temporarily restores the payload into the database field.

#### Purging payloads

[](#purging-payloads)

After restoring a payload it again occupies database space. Use the `Purge` action to remove the payload permanently when it is no longer needed.

If you schedule `php artisan flow:purge`, purging will run automatically.

### 4.4 Metrics

[](#44-metrics)

The package ships with three universal metrics. Below we describe each of them.

#### NewItems

[](#newitems)

A universal metric implementing the `Laravel\Nova\Metrics\Value` metric.

Example usage:

```
// app/Nova/Document.php

use Perfocard\Flow\Nova\Metrics\NewItems;

class Document extends Resource
{
    public function cards(NovaRequest $request)
    {
        return [
            NewItems::make(__('New documents'), self::$model),
        ];
    }
}
```

#### ItemsPerDay

[](#itemsperday)

A universal metric implementing the `Laravel\Nova\Metrics\Trend` metric.

Example usage:

```
// app/Nova/Document.php

use Perfocard\Flow\Nova\Metrics\ItemsPerDay;

class Document extends Resource
{
    public function cards(NovaRequest $request)
    {
        return [
            ItemsPerDay::make(__('Documents per day'), self::$model),
        ];
    }
}
```

#### ItemsByEnum

[](#itemsbyenum)

A universal metric implementing the `Laravel\Nova\Metrics\Partition` metric.

Example usage:

```
// app/Nova/Document.php

use Perfocard\Flow\Nova\Metrics\ItemsByEnum;
use App\Models\DocumentStatus;

class Document extends Resource
{
    public function cards(NovaRequest $request)
    {
        return [
            ItemsByEnum::make(__('Documents by status'), self::$model, DocumentStatus::class, 'status'),
        ];
    }
}
```

###  Health Score

45

—

FairBetter than 93% of packages

Maintenance88

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity56

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 ~14 days

Recently: every ~48 days

Total

15

Last Release

61d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9ee3cd0b38434e9b288941933661c44a2ae54e8fba0a2a2f979920f1f02ff0f2?d=identicon)[perfocard](/maintainers/perfocard)

---

Top Contributors

[![goszowski](https://avatars.githubusercontent.com/u/10208931?v=4)](https://github.com/goszowski "goszowski (53 commits)")

---

Tags

phplaravelprocessingeventseloquentqueueworkflowstatusflow

### Embed Badge

![Health badge](/badges/perfocard-flow/health.svg)

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

###  Alternatives

[spiritix/lada-cache

A Redis based, automated and scalable database caching layer for Laravel

591444.8k2](/packages/spiritix-lada-cache)[io238/laravel-iso-countries

Ready-to-use Laravel models and relations for country (ISO 3166), language (ISO 639-1), and currency (ISO 4217) information with multi-language support.

5462.3k](/packages/io238-laravel-iso-countries)[sebastiaanluca/laravel-boolean-dates

Automatically convert Eloquent model boolean attributes to dates (and back).

40111.7k1](/packages/sebastiaanluca-laravel-boolean-dates)[matchory/elasticsearch

The missing elasticsearch ORM for Laravel!

3059.0k](/packages/matchory-elasticsearch)[kiwilan/typescriptable-laravel

PHP package for Laravel to type Eloquent models, routes, Spatie Settings with autogenerated TypeScript. If you want to use some helpers with Inertia, you can install associated NPM package.

3920.9k](/packages/kiwilan-typescriptable-laravel)

PHPackages © 2026

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