PHPackages                             omnitech-solutions/flowlight - 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. omnitech-solutions/flowlight

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

omnitech-solutions/flowlight
============================

Lightweight workflow orchestration library for PHP. Provides a clean, composable pattern for chaining actions into pipelines, handling success and failure consistently, and keeping business logic organized and testable. Highly inspired by Ruby’s LightService gem (https://github.com/adomokos/light-service).

v0.2.0(8mo ago)02MITPHPPHP ^8.1|^8.2|^8.3

Since Aug 31Pushed 8mo agoCompare

[ Source](https://github.com/omnitech-solutions/flowlight)[ Packagist](https://packagist.org/packages/omnitech-solutions/flowlight)[ RSS](/packages/omnitech-solutions-flowlight/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (14)Versions (3)Used By (0)

Flowlight — LightService‑style Guide
====================================

[](#flowlight--lightservicestyle-guide)

---

Table of Contents
-----------------

[](#table-of-contents)

- [Why Flowlight?](#why-flowlight)
- [Features](#features)
- [Installation](#installation)
- [Project Structure](#project-structure)
- [Core Concepts](#core-concepts)
    - [Context](#context)
    - [Action](#action)
    - [Organizer](#organizer)
- [Failure &amp; Control Flow](#failure--control-flow)
    - [Outcomes](#outcomes)
    - [withErrors (merge only)](#witherrors-merge-only)
    - [withErrorsThrowAndReturn (merge + stop)](#witherrorsthrowandreturn-merge--stop)
    - [throwAndReturn (message/code + stop)](#throwandreturn-messagecode--stop)
    - [Internal control‑flow exception: JumpWhenFailed](#internal-control-flow-exception-jumpwhenfailed)
    - [WithErrorHandler (unexpected throws → context failure)](#witherrorhandler-unexpected-throws--context-failure)
- [Usage](#usage)
    - [Quick Start (Organizer)](#quick-start-organizer)
    - [Validator Action pattern](#validator-action-pattern)
    - [Service code with WithErrorHandler](#service-code-with-witherrorhandler)
    - [Reading results](#reading-results)
- [Testing Guidelines](#testing-guidelines)
- [Planned / Not Implemented](#planned--not-implemented)
- [Minimal API Reference](#minimal-api-reference)

---

Why Flowlight?
--------------

[](#why-flowlight)

Business flows grow complex quickly: validation, mapping, persistence, notifications, branching. **Flowlight** keeps each step small and composable, carrying state via a single **Context** so the code reads like a story:

```
(Validate) → (Normalize) → (Persist) → (Notify)

```

Features
--------

[](#features)

- **Composable pipelines** — Actions and Organizers chain clearly.
- **Validation as data** — accumulate errors; stop intentionally.
- **Unified exception capture** — normalize unexpected throws into the Context.
- **Lightweight** — PHP ≥ 8.2, minimal deps.

> **Not Implemented (TBD)**: lifecycle hooks (before/after/around), skip‑remaining, expects/promises, structured logging.

Installation
------------

[](#installation)

```
composer require omnitech-solutions/flowlight
```

Project Structure
-----------------

[](#project-structure)

```
src/
  Action.php
  Organizer.php
  Context.php
  Enums/ContextStatus.php
  Traits/WithErrorHandler.php
  Exceptions/
    ContextFailedError.php
    JumpWhenFailed.php

```

Core Concepts
-------------

[](#core-concepts)

### Context

[](#context)

Carries inputs, params, errors, resources, and diagnostics.

- Errors are grouped by key (e.g., `email`) with a `base` bucket for global messages.
- Diagnostics live under `internalOnly` (e.g., `message`, `error_code`, `errorInfo`).
- Public callers consume `success()` / `failure()` and `errorsArray()`.

### Action

[](#action)

Extend `Flowlight\Action` and implement `perform(Context $ctx): void`.

```
use Flowlight\Action;
use Flowlight\Context;

class CalculateDiscount extends Action
{
    protected function perform(Context $ctx): void
    {
        $amount = $ctx->paramsArray()['amount'] ?? null;
        if (!is_numeric($amount)) {
            $ctx->withErrors(['amount' => 'must be numeric']);
            $ctx->throwAndReturn('Validation failed'); // control flow unwinds
        }

        $ctx->withParams(['discount' => (float)$amount * 0.1]);
        // completion is set internally when appropriate
    }
}
```

### Organizer

[](#organizer)

Declares a sequence of steps; each step receives the same Context.

- Define steps by overriding `protected static function steps(): array`.
- Call via `Organizer::call(array $input = [], array $overrides = [], ?callable $transformContext = null): Context`.

```
use Flowlight\Organizer;

class CheckoutOrganizer extends Organizer
{
    protected static function steps(): array
    {
        return [
            \App\Actions\ValidateCheckout::class,
            \App\Actions\CalculateDiscount::class,
            \App\Actions\ChargePayment::class,
            \App\Actions\SendReceipt::class,
        ];
    }
}
```

Failure &amp; Control Flow
--------------------------

[](#failure--control-flow)

### Outcomes

[](#outcomes)

Use `success()` / `failure()` to decide how to render results. Public code should not depend on internal flags.

### withErrors (merge only)

[](#witherrors-merge-only)

Accumulates errors without stopping the chain.

```
$ctx->withErrors([
  'email' => ['is invalid', 'is required'],
  'base'  => ['Please correct the highlighted fields'],
]);
```

### withErrorsThrowAndReturn (merge + stop)

[](#witherrorsthrowandreturn-merge--stop)

Accumulates errors, sets an optional message/code, then stops immediately using internal control flow.

```
$ctx->withErrorsThrowAndReturn(
  ['email' => 'is invalid'],
  'Validation failed',
  ['error_code' => 1001]
);
```

**Result (illustrative) once the organizer returns:**

- `errorsArray()` ⇒ `['email' => ['is invalid'], 'base' => ['Validation failed']]`
- `internalOnly()` ⇒ `['message' => 'Validation failed', 'error_code' => 1001]`

### throwAndReturn (message/code + stop)

[](#throwandreturn-messagecode--stop)

Stops immediately with a message/code, without attaching field errors.

```
$ctx->throwAndReturn('Unauthorized');
// or
$ctx->throwAndReturn('Upstream unavailable', ['error_code' => 502]);
```

### Internal control‑flow exception: JumpWhenFailed

[](#internal-controlflow-exception-jumpwhenfailed)

Internal exception used to unwind quickly after a *throw‑and‑return* path. The organizer boundary catches it and normalizes the Context.

### WithErrorHandler (unexpected throws → context failure)

[](#witherrorhandler-unexpected-throws--context-failure)

Wrap risky code; unexpected exceptions are recorded into the Context with a human message and the pipeline is stopped. Optional `rethrow` propagates after recording.

```
use Flowlight\Traits\WithErrorHandler;

class ExternalCallService
{
    use WithErrorHandler;

    public function run(\Flowlight\Context $ctx): void
    {
        self::withErrorHandler($ctx, static function (\Flowlight\Context $c): void {
            performExternalCall(); // may throw
        }, rethrow: false);
    }
}
```

Usage
-----

[](#usage)

### Quick Start (Organizer)

[](#quick-start-organizer)

```
$out = CheckoutOrganizer::call(['amount' => 100]);

if ($out->success()) {
    echo $out->paramsArray()['discount'] ?? '';
} else {
    $errors = $out->errorsArray();
}
```

### Validator Action pattern

[](#validator-action-pattern)

Accumulate rule errors, then stop once you decide it’s terminal.

```
class ValidateCheckout extends \Flowlight\Action
{
    protected function perform(\Flowlight\Context $ctx): void
    {
        $p = $ctx->paramsArray();

        if (empty($p['email'])) {
            $ctx->withErrors(['email' => 'is required']);
        }
        if (!empty($p['age']) && $p['age'] < 18) {
            $ctx->withErrors(['age' => 'must be 18+']);
        }

        if (!empty($ctx->errorsArray())) {
            $ctx->withErrorsThrowAndReturn($ctx->errorsArray(), 'Validation failed');
        }
    }
}
```

### Service code with WithErrorHandler

[](#service-code-with-witherrorhandler)

See the trait example above. Keep validation failures (expected) separate from true exceptions (unexpected).

### Reading results

[](#reading-results)

Consume `success()` / `failure()` and `errorsArray()`; avoid internal flags.

Testing Guidelines
------------------

[](#testing-guidelines)

- **Action tests** — create Context via `Context::makeWithDefaults`, execute, assert params/resources/errors.
- **ValidatorAction tests** — feed invalid input, assert errors shape and that the organizer stops on throw‑and‑return.
- **Organizer tests** — assert short‑circuiting and happy‑path composition.
- **WithErrorHandler tests** — cover callable + Throwable‑proxy paths, and `rethrow`.

Planned / Not Implemented
-------------------------

[](#planned--not-implemented)

- Lifecycle hooks (before/after/around)
- Skip remaining (`skipRemaining()` parity)
- Expects &amp; Promises (compile/runtime guards)
- Structured logging around organizer/action boundaries

Minimal API Reference
---------------------

[](#minimal-api-reference)

**Context**

- `withErrors(array|Traversable $errs): self` — merge errors (no stop).
- `withErrorsThrowAndReturn(array|Traversable $errs, ?string $message = null, array|int $optionsOrErrorCode = []): self` — merge + stop.
- `throwAndReturn(?string $message = null, array|int $optionsOrErrorCode = []): self` — stop with message/code only.
- `errorsArray(): array` — user‑facing errors (incl. `base`).
- `internalOnly(): ArrayAccess|array` — diagnostics (`message`, `error_code`, `errorInfo`).
- `success(): bool` / `failure(): bool`

**Organizer**

- `protected static function steps(): array`
- `public static function call(array $input = [], array $overrides = [], ?callable $transformContext = null): Context`

**Traits\\WithErrorHandler**

- `withErrorHandler(Context $ctx, callable|Throwable $blockOrThrowable, bool $rethrow = false): void`

**Exceptions**

- `JumpWhenFailed` — internal control‑flow exception.
- `Exceptions\ContextFailedError` — exception carrying a Context.

###  Health Score

28

—

LowBetter than 54% of packages

Maintenance59

Moderate activity, may be stable

Popularity2

Limited adoption so far

Community2

Small or concentrated contributor base

Maturity42

Maturing project, gaining track record

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

2

Last Release

257d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/68cba8079721e4a471d0b0e85a0ad43e84833f9a959b9732f81128df09b8c8ac?d=identicon)[desoleary](/maintainers/desoleary)

---

Tags

phpworkflowactionspipelineorchestrationservice objectLightService

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/omnitech-solutions-flowlight/health.svg)

```
[![Health](https://phpackages.com/badges/omnitech-solutions-flowlight/health.svg)](https://phpackages.com/packages/omnitech-solutions-flowlight)
```

###  Alternatives

[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[psalm/plugin-laravel

Psalm plugin for Laravel

3274.9M308](/packages/psalm-plugin-laravel)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[flarum/core

Delightfully simple forum software.

211.3M1.9k](/packages/flarum-core)[laragear/preload

Effortlessly make a Preload script for your Laravel application.

119363.5k](/packages/laragear-preload)[tehwave/laravel-achievements

Simple, elegant Achievements the Laravel way

7012.8k](/packages/tehwave-laravel-achievements)

PHPackages © 2026

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